Set some user parameters

sampleID <- "all" #a name for your sample, should be the same as the "assay = assayID" in the box below
outdir <- "/Volumes/STNNHPC-Q1139/Laura/Covid19_ST/2_output/20210409_PreliminaryPipeline/outdir_alltissues/" #where to store the output
df <- readRDS("/Volumes/STNNHPC-Q1139/Laura/Covid19_ST/2_output/20210409_Demultiplex/outdir/covid.RDS")

Load additional R packages and functions that we will run below

library(Seurat)
library(ggplot2)
library(dplyr)
library(clustree)
library(scran)
library(scater)
library(PCAtools)
library(tibble)
library(SingleCellExperiment)
to.pdf <- function(expr, filename, ...) {
  pdf(filename, ...)
  on.exit(dev.off())
  print(eval.parent(substitute(expr)))
}
# function to remove cells with high mitochondrial/ribosomal percentages
func_assessMT.RT <- function(seuratObj, sampleID) {
  # USAGE: mySCE <- func_assessMT.RT(seuratObj, sampleID)
  # find percentage of mt/rb genes per cell
  # Human-specific. For mouse, change to "^mt-" (mitochondria) or "^Rps|^Rpl" (ribosomes)
  seuratObj[["percent.mt"]] <- PercentageFeatureSet(seuratObj, pattern = "^MT-")
  seuratObj[["percent.rb"]] <- PercentageFeatureSet(seuratObj, pattern = "^RPS|^RPL")
  
  # define some graph functions which will be run with `to.pdf` later
  ## spatial map of mitochondrial expression
  fig.mitochondrialPercentage.spatial <- function() {
    SpatialFeaturePlot(seuratObj, features = "percent.mt") +
      ggtitle(paste0(sampleID, " - percentage mitochondrial genes per spot")) +
      theme(legend.position = "right")
  }
  ## mitochondrial % only
  fig.mitochondriaPercentage <- function() {
    VlnPlot(seuratObj, features = c("percent.mt")) +
      ggtitle(paste0(sampleID, " - percentage mitochondrial genes per cell")) +
      ylab("% mitochondrial genes") +
      geom_hline(yintercept = 20, linetype = "dashed", color = "blue") +
      theme(plot.title = element_text(size = 12, face = "bold")) +
      theme_bw() +
      theme(legend.position = "none")
  }
  ## spatial map of ribosomal expression
  fig.ribosomePercentage.spatial <- function() {
    SpatialFeaturePlot(seuratObj, features = "percent.rb") +
      ggtitle(paste0(sampleID, " - percentage ribosomal genes per spot")) +
      theme(legend.position = "right")
  }
  ## ribosomal % only
  fig.ribosomePercentage <- function() {
    VlnPlot(seuratObj, features = c("percent.rb")) +
      ggtitle(paste0(sampleID, " - percentage ribosomal genes per cell")) +
      ylab("% ribosomal genes") +
      geom_hline(yintercept = 50, linetype = "dashed", color = "blue") +
      theme(plot.title = element_text(size = 12, face = "bold")) +
      theme_bw() +
      theme(legend.position = "none")
  }
  ## mitochondria % vs nFeatures
  fig.mitochondriaVSfeatures <- function() {
    basicplot <- FeatureScatter(seuratObj, feature1 = "nFeature_Spatial", feature2 = "percent.mt", pt.size = 0.5)
    pearson <- basicplot$labels$title
    basicplot +
      ggtitle(paste0(sampleID, " - percentage mitochondrial genes vs number of genes per cell (pearson = ", pearson, ")")) +
      ylab("% mitochondrial genes") +
      xlab("number of genes") +
      geom_hline(yintercept = c(10, 20), linetype="dashed", color = "blue") +
      geom_vline(xintercept = c(3000), linetype="dashed", color = "blue") +
      theme(plot.title = element_text(size = 12, face = "bold")) +
      theme_bw() +
      theme(legend.position = "none")
  }
  ## ribosomal % vs nFeatures
  fig.ribosomeVSfeatures <- function() {
    basicplot <- FeatureScatter(seuratObj, feature1 = "nFeature_Spatial", feature2 = "percent.rb", pt.size = 0.5)
    pearson <- basicplot$labels$title
    basicplot +
      ggtitle(paste0(sampleID, " - percentage ribosomal genes vs number of genes per cell (pearson = ", pearson, ")")) +
      ylab("% ribosomal genes") +
      xlab("number of genes") +
      geom_hline(yintercept = 50, linetype="dashed", color = "blue") +
      geom_vline(xintercept = c(3000), linetype="dashed", color = "blue") +
      theme(plot.title = element_text(size = 12, face = "bold")) +
      theme_bw() +
      theme(legend.position = "none")
  }
  
  # Run the figure functions and save graphs as PDFs
  ## if you just want to view the figures, just run the function name e.g. `fig.mitochondriaPercentage()`
  to.pdf(fig.mitochondriaPercentage(), paste0(outdir, sampleID, "_percentMitochondria_unfiltered.pdf"))
  to.pdf(fig.mitochondrialPercentage.spatial(), paste0(outdir, sampleID, "_percentMitochondriaSpatial_unfiltered.pdf"))
  to.pdf(fig.ribosomePercentage(), paste0(outdir, sampleID, "_percentRibosome_unfiltered.pdf"))
  to.pdf(fig.ribosomePercentage.spatial(), paste0(outdir, sampleID, "_percentRibosomeSpatial_unfiltered.pdf"))
  to.pdf(fig.mitochondriaVSfeatures(), paste0(outdir, sampleID, "_percentMitochondriaVsnFeatures.pdf"))
  to.pdf(fig.ribosomeVSfeatures(), paste0(outdir, sampleID, "_percentRibosomesVsnFeatures.pdf"))
  
  # subset input data to cells under mt/rb thresholds
  # NOTE: Though the following command looks wrong (extra ,) but it works (`SingleCellExperiment` manual, function `SCE-combine`, p12)
  seuratObj <- subset(seuratObj, subset = percent.mt < 50 & percent.rb < 50) 
  # return mySCE to main R environment
  return(seuratObj)
}
# ------------------------------------------------------------------
# QC - CELL CYCLE
# ------------------------------------------------------------------
func_predictCellCycle <- function(seuratObj, myspecies="mouse"){
  # USAGE: seuratObj <- func_predictCellCycle(seuratObj, "mouse")
  # OUTPUT: a Seurat object with S/G2M-phase scores and cell stage (G1, S, G2M) calls
  
  # specify the gene set used for Cell Cycle Scoring (human or mouse)
  if (identical(myspecies, "mouse")) {
    load("/Volumes/STNNHPC-Q1139/Laura/Covid19_ST/1_code/mouse.cc.genes.Rdata")
    geneset <- mouse.cc.genes
  } else if (identical(myspecies, "human")) {
    geneset <- cc.genes.updated.2019
  } else {
    stop("The 'species' argument must be mouse or human")
  }
  
  # make a Seurat object, normalise, run prediction
  # note: we use Seurat's default normalisation tool for the cell phase assessment (quick and dirty). Later we will use Scran for the normal normalisation
  seuratObj <- NormalizeData(seuratObj,
                             normalization.method = "LogNormalize",
                             scale.factor = 10000)
  seuratObj <- CellCycleScoring(seuratObj,
                                s.features = geneset$s.genes,
                                g2m.features = geneset$g2m.genes,
                                set.ident = TRUE)
  
  # define some graph functions which will be run with `to.pdf` later
  fig.cellcycle.bar <- function() {
    myscale <- round(max(table(seuratObj$Phase)), -3) #scale
    mybar <- barplot(table(seuratObj$Phase),
                     ylim = (c(0, myscale)),
                     main = paste0("Cell Phases in ", sampleID),
                     xlab = "cell phase",
                     ylab = "# cells", 
                     col = "white")
    text(mybar,
         table(seuratObj$Phase)+100,
         paste("n: ", table(seuratObj$Phase), sep=""), cex = 1) 
  }
  
  fig.cellcycle.pie <- function() {
    pie(table(seuratObj$Phase),
        labels = table(seuratObj$Phase),
        col = c("bisque", "cornflowerblue", "cadetblue2"),
        main = paste0("Cell phases in ", sampleID))
    legend("topright", c("G1", "G2M", "S"), cex = 0.8, fill = c("bisque", "cornflowerblue", "cadetblue2"))
  }
  
  # spatial plots
  fig.cellcycle.spatial <- function() {
    SpatialDimPlot(seuratObj, group.by = "Phase") +
      theme(legend.position = "right")
  }
  
  # Run the figure functions and save graphs as PDFs
  to.pdf(fig.cellcycle.bar(), paste0(outdir, sampleID, "_CellCycle_bar.pdf"))
  to.pdf(fig.cellcycle.pie(), paste0(outdir, sampleID, "_CellCycle_pie.pdf"))
  to.pdf(fig.cellcycle.spatial(), paste0(outdir, sampleID, "_CellCycle_spatial.pdf"))
    
  # return the updated SCE
  return(seuratObj)
}
# ------------------------------------------------------------------
# NORMALISATION
# ------------------------------------------------------------------
# function to normalise count data in scran/scater
func_scranNorm <- function(seuratObj) {
  # USAGE: mySCE <- func_scranNorm(seuratObj)
  # OUTPUT: a SCE object with (natural log) normalised counts - the counts need to be added into the Seurat object, but indirectly to keep the image data
  # NOTE: usually, scran normalisation produces log2counts, but here we produce seurat-compatible lncounts
  
  # convert to SCE object
  mySCE <- as.SingleCellExperiment(seuratObj)
  # calculate size factors and perform normalisation
  scranclusters <- quickCluster(mySCE)
  mySCE <- computeSumFactors(mySCE, clusters = scranclusters)
  # "scran sometimes calculates negative or zero size factors which will completely distort the normalized expression matrix". Let's check
  minsizefactor <- min(sizeFactors(mySCE))
  if (minsizefactor < 0) {
    warning("ALERT! scran normalisation has produced negative or zero size factors which will distort the normalised expression matrix. Proceed with care!\n You can try increasing the cluster and pool sizes until they are all positive\n See https://biocellgen-public.svi.edu.au/mig_2019_scrnaseq-workshop/public/normalization-confounders-and-batch-correction.html")
  }
  mySCE <- scater::logNormCounts(mySCE, log = FALSE, name = "unlog.normcounts")
  
  # natural-log transform counts and convert back to sparse matrix format
  assay(mySCE, "ln.normcounts") <- as(log(x = assay(mySCE, "unlog.normcounts") + 1), "dgCMatrix")
  return(mySCE)
  # NOTE: To convert to Seurat object from now on your must run:
  # seuratObj <- as.Seurat(mySCE, counts = "counts", data = "ln.normcounts")
}
func_ScaleData <- function(seuratObj) {
  # USAGE: seuratObj <- func_ScaleData(mySCE)
  # OUTPUT: a Seurat object with scaled normalised counts
  # find variable features, perform scaling
  seuratObj <- FindVariableFeatures(seuratObj, selection.method = "vst", nfeatures = 2000)
  seuratObj <- ScaleData(seuratObj)
  
  # convert to SCE object
  #mySCE <- as.SingleCellExperiment(seuratObj)
  # alternatively, don't convert back fresh, just insert the scaledata as a new assay type
  #return(mySCE)
  
  # for now, just return the Seurat object
  return(seuratObj)
}
# ------------------------------------------------------------------
# PCA
# ------------------------------------------------------------------
# function to find variable features and scale data using Seurat
func_runPCA <- function(seuratObj, runJackstraw = "TRUE") {
  # USAGE: seuratObj <- func_runPCA(seuratObj, runJackstraw = "TRUE" or "FALSE")
  # OUTPUT: a Seurat object with PCA run
  
  # Run PCA
  seuratObj <- RunPCA(seuratObj, features = VariableFeatures(object = seuratObj), npcs = 50)
  
  # calculate variance explained by each PC
  total_variance <- seuratObj@reductions$pca@misc$total.variance
  eigValues <- (seuratObj[["pca"]]@stdev)^2
  varExplained <- eigValues / total_variance
  varExplained.cum <- cumsum(varExplained)
  ### how many PCs before 20 % of the variance is explained?
  var.20pc <- sum(varExplained.cum <= 0.2)
  ### how much variance do 50 PCs explain?
  varpc.50PCA <- 100*(varExplained.cum[50])
  print(paste0("The first 50 PCs explain ", round(varpc.50PCA), "% of the variance. 20% of the variance is explained by the first ", var.20pc, " PCs"))
  
  # define some graph functions which will be run with `to.pdf` later
  ## scree plot
  fig.scree <- function() {
    varExplained %>% enframe(name = "PC", value = "varExplained" ) %>%
      ggplot(aes(x = PC, y = varExplained)) + 
      theme_bw() +
      geom_bar(stat = "identity") +
      theme_classic() +
      ggtitle(paste0(sampleID, ": scree plot")) +
      ylab("explained variance")
  }
  ## cumulative variance
  fig.cumulativeVar <- function() {
    ggplot(as.data.frame(varExplained.cum), aes(y = varExplained.cum, x = seq(1, length(varExplained.cum)))) +
      geom_point(size = 1) +
      theme_bw() +
      ggtitle("cumulative variance explained by increasing PCs") +
      xlab("PCs") +
      ylab("cumulative explained variance") +
      geom_hline(yintercept = c(0.2), linetype = "dashed", color = "blue") +
      geom_vline(xintercept = c(20), linetype = "dashed", color = "blue")
  }
  
  # Make an elbow plot with elbow point annotated (adapted from Seurat's ElbowPlot() but to show all tested PCs)
  fig.elbow <- function() {
    ElbowPlot(seuratObj, ndims = 50, reduction = "pca") +
      theme_bw() +
      ggtitle(paste0(sampleID, ": elbow plot of standard deviations of principal components"))
  }
  
  # Perform JackStraw analysis
  if (runJackstraw == "TRUE") {
    seuratObj <- JackStraw(seuratObj, num.replicate = 100, dims = 50)
    seuratObj <- ScoreJackStraw(seuratObj, dims = 1:50) # because `RunPCA` calculates 50x PCs by defalt (you can change this)
    fig.jackstraw <- function() {
      JackStrawPlot(seuratObj, dims = 1:50) +
        ggtitle("PCA JackStraw")
    }
    # the PC p-vals are in seuratObj@reductions$pca@jackstraw$overall.p.values
    # get the PC number of the last PC before one is not significant
    jscores <- as.data.frame(seuratObj@reductions$pca@jackstraw$overall.p.values > 0.05)
    chosen.jack <- as.numeric(rownames(jscores[jscores$Score == "TRUE", ][1,])) - 1
    to.pdf(fig.jackstraw(), paste0(outdir, sampleID, "_PCA_jackstraw.pdf"))
    } else {
      if (runJackstraw == "FALSE") {
      print("skipping Jackstraw analysis")
    } else {
      stop("runJackstraw must be TRUE or FALSE")
    }
  }
  
  # Run the figure functions and save graphs as PDFs
  to.pdf(fig.scree(), paste0(outdir, sampleID, "_scree.pdf"))
  to.pdf(fig.cumulativeVar(), paste0(outdir, sampleID, "_cumulativeVariance.pdf"))
  to.pdf(fig.elbow(), paste0(outdir, sampleID, "_PCA_elbow.pdf"))
  # to.pdf(fig.jackstraw(), paste0(outdir, "figs/", sampleID, "_PCA_jackstraw.pdf")) #run above in if/else bit
  # for now, just return the Seurat object
  return(seuratObj)
}
func_runNonLinearDR <- function(seuratObj, runTSNE = "TRUE") {
  # USAGE: seuratObj <- func_runNonLinearDR(seuratObj, runTSNE = "TRUE" or "FALSE")
  # OUTPUT: a Seurat object with tSNE and UMAP coordinates
  
  # Run UMAP
  seuratObj <- Seurat::RunUMAP(seuratObj, dims = 1:20, n.neighbors = 5, min.dist = 0.1)
  fig.umap.raw <- function() {
    # alternative to DimPlot(seuratObj, reduction = "umap")
    Embeddings(seuratObj, reduction = "umap") %>%
      as.data.frame() %>%
      ggplot(aes(x = UMAP_1, y = UMAP_2)) +
      geom_point(size = 0.3) +
      theme_bw(base_size = 14) +
      ggtitle(paste0(sampleID, ": UMAP"))
  }
  to.pdf(fig.umap.raw(), paste0(outdir, sampleID, "_UMAP_raw.pdf"))
  
  # Run tSNE
  if (runTSNE == "TRUE") {
    seuratObj <- Seurat::RunTSNE(seuratObj, dims = 1:50)
    
    fig.tSNE.raw <- function() {
      # alternative to DimPlot(seuratObj, reduction = "tSNE")
      Embeddings(seuratObj, reduction = "tsne") %>%
        as.data.frame() %>%
        ggplot(aes(x = tSNE_1, y = tSNE_2)) +
        geom_point(size = 0.3) +
        theme_bw(base_size = 14) +
        ggtitle(paste0(sampleID, ": tSNE"))
    }
    to.pdf(fig.tSNE.raw(), paste0(outdir, sampleID, "_tSNE_raw.pdf"))
  } else {
    if (runTSNE == "FALSE") {
      print("skipping tSNE plot")
    } else {
      stop("runTSNE must be TRUE or FALSE")
    }
  }
  return(seuratObj)
}

QC 1 - REMOVE LOW COUNTS

# look at the number of features and cells
df
An object of class Seurat 
36601 features across 2997 samples within 1 assay 
Active assay: Spatial (36601 features, 0 variable features)

Now we will look at QC plots. The nFeature measure shows us the number of genes per spot, while nCount refers to the number of RNA transcripts (i.e. total counts) per spot. We can either visualise these measures on their own as violin plots, or we can plot them together on a scatter plot, where we expect the trend to be roughly diagonal. If we see outliers from this diagonal, they are indicative of weird spots.

# Look at some QC plots
VlnPlot(df, features = c("nFeature_Spatial", "nCount_Spatial"), group.by = "orig.ident")

ggsave(paste0(outdir, sampleID, "_countsAndFeatures.pdf"))
Saving 12 x 7.42 in image
FeatureScatter(df, feature1 = "nFeature_Spatial", feature2 = "nCount_Spatial", group.by = "orig.ident") + NoLegend()

ggsave(paste0(outdir, sampleID, "_scatter.pdf"))
Saving 12 x 7.42 in image

Filter out spots with low counts and features (requires at least 100 counts and 100 feature per spot)

df <- subset(df, subset = nCount_Spatial > 100 & nFeature_Spatial > 100)
df
An object of class Seurat 
36601 features across 2969 samples within 1 assay 
Active assay: Spatial (36601 features, 0 variable features)

QC 2 - MITOCHONDRIA AND RIBOSOMES

Now we’ll look for spots with excessively high percentages of ribosomal or mitochondrial genes, which may further indicate a quality problem. We’re arbitrarily going to filter spots with >50% mitochondrial genes and/or >50% ribosomal genes. To see the “before filtering” plots, have a look at the accompanying plots directory.

df <- func_assessMT.RT(df, sampleID)
# this is what the data look like, post-filtering
SpatialFeaturePlot(df, features = "percent.mt") + theme(legend.position = "right")

ggsave(paste0(outdir, sampleID, "_percentMitochondriaSpatial_filtered.pdf"))
Saving 8 x 4.94 in image
SpatialFeaturePlot(df, features = "percent.rb") + theme(legend.position = "right")

ggsave(paste0(outdir, sampleID, "_percentRibosomeSpatial_filtered.pdf"))
Saving 8 x 4.94 in image

And just check in on how many genes/cells remain in our dataset:

df
An object of class Seurat 
36601 features across 2969 samples within 1 assay 
Active assay: Spatial (36601 features, 0 variable features)

QC 3 - CELL CYCLE ANNOTATION

Now we will do a cell cycle prediction. This method looks at certain marker genes associated with different phases of mitosis, and is described in a Seurat vignette. This prediction is typically used for single cell data, so it’s possible it won’t perform as well here with ST data. For more information about this analysis, see the Seurat vignette. However, unlike in the Seurat vignette, we aren’t going to include this data in any regression steps - we are just interested in seeing the trends across our tissue.

df <- func_predictCellCycle(df, "human")
Performing log-normalization
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
NULL
$rect
$rect$w
[1] 0.2917558

$rect$h
[1] 0.267907

$rect$left
[1] 0.9138256

$rect$top
[1] 1.08


$text
$text$x
[1] 1.054477 1.054477 1.054477

$text$y
[1] 1.0130233 0.9460465 0.8790698

Let’s visualise the results. We’ll grey out the G1-phase spots so we highlight those that are dividing.

SpatialDimPlot(df, group.by = "Phase") + theme(legend.position = "right") + scale_fill_manual(values = c("grey", "#F4A698", "#DD614A", "black"))
Scale for 'fill' is already present. Adding another scale for 'fill', which will replace the existing
scale.

ggsave(paste0(outdir, sampleID, "_CellCycle_spatialPretty.pdf"))
Saving 10 x 10 in image

NORMALISATION IN SCRAN

Here we diverge from the Seurat pipeline to run Scran normalisation instead.

df.sce <- func_scranNorm(df)
df.temp <- as.Seurat(df.sce, counts = "counts", data = "ln.normcounts")
df@assays$Spatial@counts <- df.temp@assays$RNA@counts
df@assays$Spatial@data <- df.temp@assays$RNA@data

RUN PCA AND UMAP

df <- func_ScaleData(df)
Calculating gene variances
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Calculating feature variances of standardized and clipped values
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Centering and scaling data matrix

  |                                                                                                        
  |                                                                                                  |   0%
  |                                                                                                        
  |=================================================                                                 |  50%
  |                                                                                                        
  |==================================================================================================| 100%
df <- func_runPCA(df, runJackstraw = "FALSE")
PC_ 1 
Positive:  IGKC, AD000090.1, MTRNR2L12, PIM3, IGSF6, IGLC7, QSER1, AKAP9, RALGPS2, LINC01783 
       POC1B-AS1, RAPGEF4, AC007406.5, ARMT1, ZNF254, TTC39B, USP49, SIVA1, ATPSCKMT, TENT4A 
       DPEP2, RAB30, Z83843.1, ZMYND10, TMEM138, ZNF720, SLC35B2, ADGRF4, PKDCC, ADCY5 
Negative:  COL1A1, HLA-B, TIMP1, DCN, HLA-A, SPARC, COL1A2, COL3A1, AC007952.4, B2M 
       IFI6, EFEMP1, CXCL10, ADH1B, IGFBP7, SERPINH1, MT-ATP6, CLU, MIF, MT-CO2 
       IGHG4, PDIA3, VCAN, C1QC, MT-ND4, RARRES1, PPIB, PRG4, IGHA1, ZFP36L1 
PC_ 2 
Positive:  IGKC, HLA-B, B2M, IFI6, COL3A1, TIMP1, SCGB3A1, DCN, HLA-A, IGHG1 
       IGLC1, CLU, SERPINF1, IGHA1, HSD11B1, HBB, COL1A1, TNC, EEF1D, JCHAIN 
       METTL7A, IGHM, XIST, FHL1, IGHG4, ADAMTSL2, AC007952.4, SRGN, COMMD3, PRG4 
Negative:  AD000090.1, MALAT1, MT-ND2, MT-CYB, MT-ND4, MT-ND1, MT-ND3, MTRNR2L12, MT-ATP6, AL627171.2 
       MT-CO1, MT-ND5, MT-CO2, PABPC1, MTHFR, AASS, STRIP1, GJA4, RMRP, TRIB3 
       PHLDA2, MACF1, WDR48, UBXN7, MARCH7, SGPL1, UBE2V2, DHCR7, RBMS2, RBM39 
PC_ 3 
Positive:  TGFB1I1, IGLC1, PIM3, IGLC2, SCGB3A1, LRRC8A, PTDSS1, DELE1, ATP11B, USP33 
       PSMB10, NOTCH2, SMIM15, IRF2, SLBP, PEMT, AQP3, MT-ND3, RBM39, RBMS3 
       IGKC, CPSF4, MYH10, TCP1, ARHGDIA, HERC1, PITRM1, SCO2, SCARA5, WLS 
Negative:  UBE2V2, FBN1, PAX8-AS1, POGLUT3, GALNS, TYROBP, ERAP2, CDH5, LPAR1, ABCA8 
       MRPL50, SEC31A, CSF3R, CRAT, TOR1B, DGKZ, MNDA, LMBR1, NDUFB2, AHDC1 
       VBP1, S1PR2, CSTF2T, LTV1, NCOR1, TBL2, XPNPEP1, C1QC, POGLUT1, UBE2E1 
PC_ 4 
Positive:  LTBP1, DOCK2, ARL6IP4, GNAS, CLTC, CCL21, CDC27, SIN3B, HERPUD1, HVCN1 
       NCOR1, SERPINE2, UBXN1, PSMB8-AS1, ANKRD17, PPM1G, NCSTN, DRAM2, CMTM7, ZNF24 
       PDGFB, FAM3C, MNDA, GJA4, FAM20A, ANP32B, CHD9, TARS, DHCR7, RAP1A 
Negative:  CCDC92, SFRP2, NCOR2, NCEH1, EFEMP1, GLG1, RAP2A, PBDC1, LPAR1, ATP6V1C1 
       STEAP4, MMP1, RAB11A, WBP1, BAIAP2, XPNPEP1, SELENOS, REPS1, PRDX3, PITRM1 
       NDUFS8, TUBB2A, DLAT, SFSWAP, TRIM69, POLG, SACM1L, MZB1, HGH1, DDR2 
PC_ 5 
Positive:  PDIA3, HGF, ENPEP, CKAP4, NDUFS4, ST13, HERC6, CXCL14, MTHFR, PEMT 
       NAPRT, SSBP3, PTPRK, NIPA2, EIF3I, TXLNA, FHL1, RAP1A, SFSWAP, LRRFIP1 
       MX2, RARRES1, TFRC, PDLIM3, TRIB1, WBP1, RNPS1, SLC44A4, DCTN6, EFR3A 
Negative:  CCDC92, PPM1G, BAIAP2, ELL2, TIE1, PLXNB1, BHLHE40, DDA1, MAPK7, ACSL1 
       PIM3, CCSER2, PRG4, CHST15, WIPI1, TXN2, NCEH1, PHLDA2, CS, ARMC6 
       TMEM150A, DDX41, ZNRF1, FAM160A2, CSTF2T, GLRX, NCLN, PCNA, DLAT, LARP1 
[1] "The first 50 PCs explain 9% of the variance. 20% of the variance is explained by the first 50 PCs"
[1] "skipping Jackstraw analysis"
ElbowPlot(df, ndims = 50)
ggsave(paste0(outdir, sampleID, "_elbowplot.jpeg"))
Saving 7.29 x 4.51 in image
df <- func_runNonLinearDR(df, runTSNE = "TRUE") # 50 dims
The default method for RunUMAP has changed from calling Python UMAP via reticulate to the R-native UWOT using the cosine metric
To use Python UMAP via reticulate, set umap.method to 'umap-learn' and metric to 'correlation'
This message will be shown once per session13:15:05 UMAP embedding parameters a = 1.577 b = 0.8951
13:15:05 Read 2969 rows and found 20 numeric columns
13:15:05 Using Annoy for neighbor search, n_neighbors = 5
13:15:05 Building Annoy index with metric = cosine, n_trees = 50
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
13:15:05 Writing NN index file to temp file /var/folders/8w/nx6ng4zj0rg143r293dq3mf40000gp/T//Rtmp7bVTx4/file69b6bf5c152
13:15:05 Searching Annoy index using 1 thread, search_k = 500
13:15:06 Annoy recall = 100%
13:15:06 Commencing smooth kNN distance calibration using 1 thread
13:15:08 Initializing from normalized Laplacian + noise
13:15:08 Commencing optimization for 500 epochs, with 16876 positive edges
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
13:15:10 Optimization finished

df <- FindNeighbors(df, reduction = "pca", dims = 1:20)
Computing nearest neighbor graph
Computing SNN
DimPlot(df, reduction = "umap", group.by = "orig.ident")

DimPlot(df, reduction = "tsne", group.by = "orig.ident")

CLUSTER AND TEST WITH CLUSTREE

Now we’re going to cluster the cells. First, we’ll make a temporary R object df.2 and test a range of different resolution values. The resolution parameter “control[s] the size and structure of communities that are formed by optimizing a generalized objective function”. Effectively, an increased resolution = more clusters - though you can’t tell Seurat to give you exactly N clusters, and often different resolution values will give the same number of clusters.

Look at the plots

DimPlot(df.2, reduction = "umap", group.by = "Spatial_snn_res.0") + ggtitle("res = 0")

DimPlot(df.2, reduction = "umap", group.by = "Spatial_snn_res.0.1") + ggtitle("res = 0.1")

DimPlot(df.2, reduction = "umap", group.by = "Spatial_snn_res.0.2") + ggtitle("res = 0.2")

DimPlot(df.2, reduction = "umap", group.by = "Spatial_snn_res.0.3") + ggtitle("res = 0.3")

DimPlot(df.2, reduction = "umap", group.by = "Spatial_snn_res.0.4") + ggtitle("res = 0.4")

DimPlot(df.2, reduction = "umap", group.by = "Spatial_snn_res.0.6") + ggtitle("res = 0.6")

DimPlot(df.2, reduction = "umap", group.by = "Spatial_snn_res.0.8") + ggtitle("res = 0.8")

DimPlot(df.2, reduction = "umap", group.by = "Spatial_snn_res.1") + ggtitle("res = 1")

DimPlot(df.2, reduction = "umap", group.by = "Spatial_snn_res.1.2") + ggtitle("res = 1.2")

DimPlot(df.2, reduction = "umap", group.by = "Spatial_snn_res.1.4") + ggtitle("res = 1.4")

DimPlot(df.2, reduction = "umap", group.by = "Spatial_snn_res.1.6") + ggtitle("res = 1.6")

SpatialDimPlot(df.2, group.by = "Spatial_snn_res.0") + ggtitle("res = 0")

SpatialDimPlot(df.2, group.by = "Spatial_snn_res.0.1",) + ggtitle("res = 0.1")

SpatialDimPlot(df.2, group.by = "Spatial_snn_res.0.2",) + ggtitle("res = 0.2")

SpatialDimPlot(df.2, group.by = "Spatial_snn_res.0.3",) + ggtitle("res = 0.3")

SpatialDimPlot(df.2, group.by = "Spatial_snn_res.0.4") + ggtitle("res = 0.4")

SpatialDimPlot(df.2, group.by = "Spatial_snn_res.0.6") + ggtitle("res = 0.6")

SpatialDimPlot(df.2, group.by = "Spatial_snn_res.0.8") + ggtitle("res = 0.8")

SpatialDimPlot(df.2, group.by = "Spatial_snn_res.1") + ggtitle("res = 1")

SpatialDimPlot(df.2, group.by = "Spatial_snn_res.1.2") + ggtitle("res = 1.2")

SpatialDimPlot(df.2, group.by = "Spatial_snn_res.1") + ggtitle("res = 1.4")

SpatialDimPlot(df.2, group.by = "Spatial_snn_res.1.2") + ggtitle("res = 1.6")

Now we want to choose a resolution value. One method to do this uses the R package Clustree. This is the R package description: “Deciding what resolution to use can be a difficult question when approaching a clustering analysis. One way to approach this problem is to look at how samples move as the number of clusters increases. This package allows you to produce clustering trees, a visualisation for interrogating clusterings as resolution increases.” It will generate a tree diagram showing how the different clusterings are inter-related. The clusters in this diagram will be coloured different shades of blue, representing “sc3 stability”. This is a “Stability index [that] shows how stable each cluster is accross the selected range of k. The stability index varies between 0 and 1, where 1 means that the same cluster appears in every solution for different k”

clust <- clustree(df.2, prefix = "Spatial_snn_res.", node_colour = "sc3_stability", edge_width = 1, node_text_colour = "white", node_label_size = 4, layout = "tree", edge_arrow = FALSE)
The `add` argument of `group_by()` is deprecated as of dplyr 1.0.0.
Please use the `.add` argument instead.
clust
ggsave(plot = clust, file = paste0(outdir, sampleID, "_clustree.pdf"), width = 10, height = 10)

# extract the stability values for the different resolutions
stability <- clust$data[,c("Spatial_snn_res.", "sc3_stability")]
write.table(stability, file = paste0(outdir, "clustree_stability.txt"), sep = "\t", quote = FALSE, row.names = FALSE, col.names = TRUE)
# here we're going to work out which of the possible clustering resolutions is the most stable (i.e. gives the highest average sc3 stability score). 
stability <- stability[stability$Spatial_snn_res. %in% names(which(table(stability$Spatial_snn_res.) > 1)), ]
stability.ave <- aggregate(as.numeric(stability$sc3_stability), list(stability$Spatial_snn_res.), mean)
rownames(stability.ave) <- stability.ave$Group.1
stability.ave$Group.1 <- NULL
stability.ave.no0 <- stability.ave[2:nrow(stability.ave), , drop = FALSE]
bestres <- as.numeric(rownames(stability.ave.no0)[which.max(stability.ave.no0$x)])
stability.ave
bestres
[1] 0.3

The value printed above is the resolution parameter that produced the highest average stability.

rm(df.2)
df <- FindClusters(df, resolution = bestres)
Modularity Optimizer version 1.3.0 by Ludo Waltman and Nees Jan van Eck

Number of nodes: 2969
Number of edges: 116001

Running Louvain algorithm...
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Maximum modularity in 10 random starts: 0.7876
Number of communities: 3
Elapsed time: 0 seconds
mycol <- paste0("Spatial_snn_res.", bestres)
DimPlot(df, group.by = mycol)
ggsave(paste0(outdir, sampleID, "UMAP_res", bestres, ".pdf"))
Saving 7.29 x 4.51 in image

SpatialDimPlot(df, group.by = mycol)
ggsave(paste0(outdir, sampleID, "spatial_res", bestres, ".pdf"))
Saving 7.29 x 4.51 in image

FIND MARKER GENES ASSOCIATED WITH EACH CLUSTER

mycol <- paste0("Spatial_snn_res.", bestres)
Idents(df) <- mycol
markers <- FindAllMarkers(df, only.pos = TRUE, min.pct = 0.25, logfc.threshold = 0.25)
Calculating cluster 0

  |                                                  | 0 % ~calculating  
  |+++++++++++++++++++++++++                         | 50% ~00s          
  |++++++++++++++++++++++++++++++++++++++++++++++++++| 100% elapsed=00s  
Calculating cluster 1

  |                                                  | 0 % ~calculating  
  |+++++++++++++++++++++++++                         | 50% ~00s          
  |++++++++++++++++++++++++++++++++++++++++++++++++++| 100% elapsed=00s  
Calculating cluster 2

  |                                                  | 0 % ~calculating  
  |++                                                | 3 % ~00s          
  |++++                                              | 7 % ~00s          
  |++++++                                            | 10% ~00s          
  |+++++++                                           | 14% ~00s          
  |+++++++++                                         | 17% ~00s          
  |+++++++++++                                       | 21% ~00s          
  |+++++++++++++                                     | 24% ~00s          
  |++++++++++++++                                    | 28% ~00s          
  |++++++++++++++++                                  | 31% ~00s          
  |++++++++++++++++++                                | 34% ~00s          
  |+++++++++++++++++++                               | 38% ~00s          
  |+++++++++++++++++++++                             | 41% ~00s          
  |+++++++++++++++++++++++                           | 45% ~00s          
  |+++++++++++++++++++++++++                         | 48% ~00s          
  |++++++++++++++++++++++++++                        | 52% ~00s          
  |++++++++++++++++++++++++++++                      | 55% ~00s          
  |++++++++++++++++++++++++++++++                    | 59% ~00s          
  |++++++++++++++++++++++++++++++++                  | 62% ~00s          
  |+++++++++++++++++++++++++++++++++                 | 66% ~00s          
  |+++++++++++++++++++++++++++++++++++               | 69% ~00s          
  |+++++++++++++++++++++++++++++++++++++             | 72% ~00s          
  |++++++++++++++++++++++++++++++++++++++            | 76% ~00s          
  |++++++++++++++++++++++++++++++++++++++++          | 79% ~00s          
  |++++++++++++++++++++++++++++++++++++++++++        | 83% ~00s          
  |++++++++++++++++++++++++++++++++++++++++++++      | 86% ~00s          
  |+++++++++++++++++++++++++++++++++++++++++++++     | 90% ~00s          
  |+++++++++++++++++++++++++++++++++++++++++++++++   | 93% ~00s          
  |+++++++++++++++++++++++++++++++++++++++++++++++++ | 97% ~00s          
  |++++++++++++++++++++++++++++++++++++++++++++++++++| 100% elapsed=00s  
top10 <- markers %>% group_by(cluster) %>% top_n(n = 10, wt = avg_log2FC)
write.table(markers, file = paste0(outdir, "allmarkers.txt"), sep = "\t", quote = FALSE, col.names = NA)
write.table(top10, file = paste0(outdir, "top10markers.txt"), sep = "\t", quote = FALSE, col.names = NA)
DoHeatmap(df, features = top10$gene) + NoLegend()
The following features were omitted as they were not found in the scale.data slot for the Spatial assay: SCGB1A1
ggsave(paste0(outdir, "top10_heatmap.pdf"))
Saving 7.29 x 4.51 in image

FIND THE NUMBER OF CELLS PER CLUSTER

table <- as.data.frame(table(df[[mycol]]))
ggplot(table, aes(x = Var1, y = Freq)) +
  geom_bar(stat = "identity") +
  coord_flip()
ggsave(file = paste0(outdir, "clustercounts.pdf"))
Saving 7.29 x 4.51 in image

rownames(table) <- table$Var1
table$Var1 <- NULL
write.table(table, file = paste0(outdir, "clustercounts.txt"), sep = "\t", quote = FALSE, col.names = NA)

SAVE THE OUTPUT

saveRDS(df, file = paste0(outdir, sampleID, "_DF_annotated.RDS"))
LS0tCnRpdGxlOiAiSHVtYW4gVmlzaXVtIEtpZG5leSBQaXBlbGluZSIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKIyBTZXQgc29tZSB1c2VyIHBhcmFtZXRlcnMKCmBgYHtyfQpzYW1wbGVJRCA8LSAiYWxsIiAjYSBuYW1lIGZvciB5b3VyIHNhbXBsZSwgc2hvdWxkIGJlIHRoZSBzYW1lIGFzIHRoZSAiYXNzYXkgPSBhc3NheUlEIiBpbiB0aGUgYm94IGJlbG93Cm91dGRpciA8LSAiL1ZvbHVtZXMvU1ROTkhQQy1RMTEzOS9MYXVyYS9Db3ZpZDE5X1NULzJfb3V0cHV0LzIwMjEwNDA5X1ByZWxpbWluYXJ5UGlwZWxpbmUvb3V0ZGlyX2FsbHRpc3N1ZXMvIiAjd2hlcmUgdG8gc3RvcmUgdGhlIG91dHB1dApkZiA8LSByZWFkUkRTKCIvVm9sdW1lcy9TVE5OSFBDLVExMTM5L0xhdXJhL0NvdmlkMTlfU1QvMl9vdXRwdXQvMjAyMTA0MDlfRGVtdWx0aXBsZXgvb3V0ZGlyL2NvdmlkLlJEUyIpCmBgYAoKIyBMb2FkIGFkZGl0aW9uYWwgUiBwYWNrYWdlcyBhbmQgZnVuY3Rpb25zIHRoYXQgd2Ugd2lsbCBydW4gYmVsb3cKCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmxpYnJhcnkoU2V1cmF0KQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkoY2x1c3RyZWUpCmxpYnJhcnkoc2NyYW4pCmxpYnJhcnkoc2NhdGVyKQpsaWJyYXJ5KFBDQXRvb2xzKQpsaWJyYXJ5KHRpYmJsZSkKbGlicmFyeShTaW5nbGVDZWxsRXhwZXJpbWVudCkKdG8ucGRmIDwtIGZ1bmN0aW9uKGV4cHIsIGZpbGVuYW1lLCAuLi4pIHsKICBwZGYoZmlsZW5hbWUsIC4uLikKICBvbi5leGl0KGRldi5vZmYoKSkKICBwcmludChldmFsLnBhcmVudChzdWJzdGl0dXRlKGV4cHIpKSkKfQojIGZ1bmN0aW9uIHRvIHJlbW92ZSBjZWxscyB3aXRoIGhpZ2ggbWl0b2Nob25kcmlhbC9yaWJvc29tYWwgcGVyY2VudGFnZXMKZnVuY19hc3Nlc3NNVC5SVCA8LSBmdW5jdGlvbihzZXVyYXRPYmosIHNhbXBsZUlEKSB7CiAgIyBVU0FHRTogbXlTQ0UgPC0gZnVuY19hc3Nlc3NNVC5SVChzZXVyYXRPYmosIHNhbXBsZUlEKQogICMgZmluZCBwZXJjZW50YWdlIG9mIG10L3JiIGdlbmVzIHBlciBjZWxsCiAgIyBIdW1hbi1zcGVjaWZpYy4gRm9yIG1vdXNlLCBjaGFuZ2UgdG8gIl5tdC0iIChtaXRvY2hvbmRyaWEpIG9yICJeUnBzfF5ScGwiIChyaWJvc29tZXMpCiAgc2V1cmF0T2JqW1sicGVyY2VudC5tdCJdXSA8LSBQZXJjZW50YWdlRmVhdHVyZVNldChzZXVyYXRPYmosIHBhdHRlcm4gPSAiXk1ULSIpCiAgc2V1cmF0T2JqW1sicGVyY2VudC5yYiJdXSA8LSBQZXJjZW50YWdlRmVhdHVyZVNldChzZXVyYXRPYmosIHBhdHRlcm4gPSAiXlJQU3xeUlBMIikKICAKICAjIGRlZmluZSBzb21lIGdyYXBoIGZ1bmN0aW9ucyB3aGljaCB3aWxsIGJlIHJ1biB3aXRoIGB0by5wZGZgIGxhdGVyCiAgIyMgc3BhdGlhbCBtYXAgb2YgbWl0b2Nob25kcmlhbCBleHByZXNzaW9uCiAgZmlnLm1pdG9jaG9uZHJpYWxQZXJjZW50YWdlLnNwYXRpYWwgPC0gZnVuY3Rpb24oKSB7CiAgICBTcGF0aWFsRmVhdHVyZVBsb3Qoc2V1cmF0T2JqLCBmZWF0dXJlcyA9ICJwZXJjZW50Lm10IikgKwogICAgICBnZ3RpdGxlKHBhc3RlMChzYW1wbGVJRCwgIiAtIHBlcmNlbnRhZ2UgbWl0b2Nob25kcmlhbCBnZW5lcyBwZXIgc3BvdCIpKSArCiAgICAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJyaWdodCIpCiAgfQogICMjIG1pdG9jaG9uZHJpYWwgJSBvbmx5CiAgZmlnLm1pdG9jaG9uZHJpYVBlcmNlbnRhZ2UgPC0gZnVuY3Rpb24oKSB7CiAgICBWbG5QbG90KHNldXJhdE9iaiwgZmVhdHVyZXMgPSBjKCJwZXJjZW50Lm10IikpICsKICAgICAgZ2d0aXRsZShwYXN0ZTAoc2FtcGxlSUQsICIgLSBwZXJjZW50YWdlIG1pdG9jaG9uZHJpYWwgZ2VuZXMgcGVyIGNlbGwiKSkgKwogICAgICB5bGFiKCIlIG1pdG9jaG9uZHJpYWwgZ2VuZXMiKSArCiAgICAgIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDIwLCBsaW5ldHlwZSA9ICJkYXNoZWQiLCBjb2xvciA9ICJibHVlIikgKwogICAgICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIikpICsKICAgICAgdGhlbWVfYncoKSArCiAgICAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikKICB9CiAgIyMgc3BhdGlhbCBtYXAgb2Ygcmlib3NvbWFsIGV4cHJlc3Npb24KICBmaWcucmlib3NvbWVQZXJjZW50YWdlLnNwYXRpYWwgPC0gZnVuY3Rpb24oKSB7CiAgICBTcGF0aWFsRmVhdHVyZVBsb3Qoc2V1cmF0T2JqLCBmZWF0dXJlcyA9ICJwZXJjZW50LnJiIikgKwogICAgICBnZ3RpdGxlKHBhc3RlMChzYW1wbGVJRCwgIiAtIHBlcmNlbnRhZ2Ugcmlib3NvbWFsIGdlbmVzIHBlciBzcG90IikpICsKICAgICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gInJpZ2h0IikKICB9CiAgIyMgcmlib3NvbWFsICUgb25seQogIGZpZy5yaWJvc29tZVBlcmNlbnRhZ2UgPC0gZnVuY3Rpb24oKSB7CiAgICBWbG5QbG90KHNldXJhdE9iaiwgZmVhdHVyZXMgPSBjKCJwZXJjZW50LnJiIikpICsKICAgICAgZ2d0aXRsZShwYXN0ZTAoc2FtcGxlSUQsICIgLSBwZXJjZW50YWdlIHJpYm9zb21hbCBnZW5lcyBwZXIgY2VsbCIpKSArCiAgICAgIHlsYWIoIiUgcmlib3NvbWFsIGdlbmVzIikgKwogICAgICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSA1MCwgbGluZXR5cGUgPSAiZGFzaGVkIiwgY29sb3IgPSAiYmx1ZSIpICsKICAgICAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpKSArCiAgICAgIHRoZW1lX2J3KCkgKwogICAgICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpCiAgfQogICMjIG1pdG9jaG9uZHJpYSAlIHZzIG5GZWF0dXJlcwogIGZpZy5taXRvY2hvbmRyaWFWU2ZlYXR1cmVzIDwtIGZ1bmN0aW9uKCkgewogICAgYmFzaWNwbG90IDwtIEZlYXR1cmVTY2F0dGVyKHNldXJhdE9iaiwgZmVhdHVyZTEgPSAibkZlYXR1cmVfU3BhdGlhbCIsIGZlYXR1cmUyID0gInBlcmNlbnQubXQiLCBwdC5zaXplID0gMC41KQogICAgcGVhcnNvbiA8LSBiYXNpY3Bsb3QkbGFiZWxzJHRpdGxlCiAgICBiYXNpY3Bsb3QgKwogICAgICBnZ3RpdGxlKHBhc3RlMChzYW1wbGVJRCwgIiAtIHBlcmNlbnRhZ2UgbWl0b2Nob25kcmlhbCBnZW5lcyB2cyBudW1iZXIgb2YgZ2VuZXMgcGVyIGNlbGwgKHBlYXJzb24gPSAiLCBwZWFyc29uLCAiKSIpKSArCiAgICAgIHlsYWIoIiUgbWl0b2Nob25kcmlhbCBnZW5lcyIpICsKICAgICAgeGxhYigibnVtYmVyIG9mIGdlbmVzIikgKwogICAgICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSBjKDEwLCAyMCksIGxpbmV0eXBlPSJkYXNoZWQiLCBjb2xvciA9ICJibHVlIikgKwogICAgICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSBjKDMwMDApLCBsaW5ldHlwZT0iZGFzaGVkIiwgY29sb3IgPSAiYmx1ZSIpICsKICAgICAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpKSArCiAgICAgIHRoZW1lX2J3KCkgKwogICAgICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpCiAgfQogICMjIHJpYm9zb21hbCAlIHZzIG5GZWF0dXJlcwogIGZpZy5yaWJvc29tZVZTZmVhdHVyZXMgPC0gZnVuY3Rpb24oKSB7CiAgICBiYXNpY3Bsb3QgPC0gRmVhdHVyZVNjYXR0ZXIoc2V1cmF0T2JqLCBmZWF0dXJlMSA9ICJuRmVhdHVyZV9TcGF0aWFsIiwgZmVhdHVyZTIgPSAicGVyY2VudC5yYiIsIHB0LnNpemUgPSAwLjUpCiAgICBwZWFyc29uIDwtIGJhc2ljcGxvdCRsYWJlbHMkdGl0bGUKICAgIGJhc2ljcGxvdCArCiAgICAgIGdndGl0bGUocGFzdGUwKHNhbXBsZUlELCAiIC0gcGVyY2VudGFnZSByaWJvc29tYWwgZ2VuZXMgdnMgbnVtYmVyIG9mIGdlbmVzIHBlciBjZWxsIChwZWFyc29uID0gIiwgcGVhcnNvbiwgIikiKSkgKwogICAgICB5bGFiKCIlIHJpYm9zb21hbCBnZW5lcyIpICsKICAgICAgeGxhYigibnVtYmVyIG9mIGdlbmVzIikgKwogICAgICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSA1MCwgbGluZXR5cGU9ImRhc2hlZCIsIGNvbG9yID0gImJsdWUiKSArCiAgICAgIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IGMoMzAwMCksIGxpbmV0eXBlPSJkYXNoZWQiLCBjb2xvciA9ICJibHVlIikgKwogICAgICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIikpICsKICAgICAgdGhlbWVfYncoKSArCiAgICAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikKICB9CiAgCiAgIyBSdW4gdGhlIGZpZ3VyZSBmdW5jdGlvbnMgYW5kIHNhdmUgZ3JhcGhzIGFzIFBERnMKICAjIyBpZiB5b3UganVzdCB3YW50IHRvIHZpZXcgdGhlIGZpZ3VyZXMsIGp1c3QgcnVuIHRoZSBmdW5jdGlvbiBuYW1lIGUuZy4gYGZpZy5taXRvY2hvbmRyaWFQZXJjZW50YWdlKClgCiAgdG8ucGRmKGZpZy5taXRvY2hvbmRyaWFQZXJjZW50YWdlKCksIHBhc3RlMChvdXRkaXIsIHNhbXBsZUlELCAiX3BlcmNlbnRNaXRvY2hvbmRyaWFfdW5maWx0ZXJlZC5wZGYiKSkKICB0by5wZGYoZmlnLm1pdG9jaG9uZHJpYWxQZXJjZW50YWdlLnNwYXRpYWwoKSwgcGFzdGUwKG91dGRpciwgc2FtcGxlSUQsICJfcGVyY2VudE1pdG9jaG9uZHJpYVNwYXRpYWxfdW5maWx0ZXJlZC5wZGYiKSkKICB0by5wZGYoZmlnLnJpYm9zb21lUGVyY2VudGFnZSgpLCBwYXN0ZTAob3V0ZGlyLCBzYW1wbGVJRCwgIl9wZXJjZW50Umlib3NvbWVfdW5maWx0ZXJlZC5wZGYiKSkKICB0by5wZGYoZmlnLnJpYm9zb21lUGVyY2VudGFnZS5zcGF0aWFsKCksIHBhc3RlMChvdXRkaXIsIHNhbXBsZUlELCAiX3BlcmNlbnRSaWJvc29tZVNwYXRpYWxfdW5maWx0ZXJlZC5wZGYiKSkKICB0by5wZGYoZmlnLm1pdG9jaG9uZHJpYVZTZmVhdHVyZXMoKSwgcGFzdGUwKG91dGRpciwgc2FtcGxlSUQsICJfcGVyY2VudE1pdG9jaG9uZHJpYVZzbkZlYXR1cmVzLnBkZiIpKQogIHRvLnBkZihmaWcucmlib3NvbWVWU2ZlYXR1cmVzKCksIHBhc3RlMChvdXRkaXIsIHNhbXBsZUlELCAiX3BlcmNlbnRSaWJvc29tZXNWc25GZWF0dXJlcy5wZGYiKSkKICAKICAjIHN1YnNldCBpbnB1dCBkYXRhIHRvIGNlbGxzIHVuZGVyIG10L3JiIHRocmVzaG9sZHMKICAjIE5PVEU6IFRob3VnaCB0aGUgZm9sbG93aW5nIGNvbW1hbmQgbG9va3Mgd3JvbmcgKGV4dHJhICwpIGJ1dCBpdCB3b3JrcyAoYFNpbmdsZUNlbGxFeHBlcmltZW50YCBtYW51YWwsIGZ1bmN0aW9uIGBTQ0UtY29tYmluZWAsIHAxMikKICBzZXVyYXRPYmogPC0gc3Vic2V0KHNldXJhdE9iaiwgc3Vic2V0ID0gcGVyY2VudC5tdCA8IDUwICYgcGVyY2VudC5yYiA8IDUwKSAKICAjIHJldHVybiBteVNDRSB0byBtYWluIFIgZW52aXJvbm1lbnQKICByZXR1cm4oc2V1cmF0T2JqKQp9CiMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiMgUUMgLSBDRUxMIENZQ0xFCiMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCmZ1bmNfcHJlZGljdENlbGxDeWNsZSA8LSBmdW5jdGlvbihzZXVyYXRPYmosIG15c3BlY2llcz0ibW91c2UiKXsKICAjIFVTQUdFOiBzZXVyYXRPYmogPC0gZnVuY19wcmVkaWN0Q2VsbEN5Y2xlKHNldXJhdE9iaiwgIm1vdXNlIikKICAjIE9VVFBVVDogYSBTZXVyYXQgb2JqZWN0IHdpdGggUy9HMk0tcGhhc2Ugc2NvcmVzIGFuZCBjZWxsIHN0YWdlIChHMSwgUywgRzJNKSBjYWxscwogIAogICMgc3BlY2lmeSB0aGUgZ2VuZSBzZXQgdXNlZCBmb3IgQ2VsbCBDeWNsZSBTY29yaW5nIChodW1hbiBvciBtb3VzZSkKICBpZiAoaWRlbnRpY2FsKG15c3BlY2llcywgIm1vdXNlIikpIHsKICAgIGxvYWQoIi9Wb2x1bWVzL1NUTk5IUEMtUTExMzkvTGF1cmEvQ292aWQxOV9TVC8xX2NvZGUvbW91c2UuY2MuZ2VuZXMuUmRhdGEiKQogICAgZ2VuZXNldCA8LSBtb3VzZS5jYy5nZW5lcwogIH0gZWxzZSBpZiAoaWRlbnRpY2FsKG15c3BlY2llcywgImh1bWFuIikpIHsKICAgIGdlbmVzZXQgPC0gY2MuZ2VuZXMudXBkYXRlZC4yMDE5CiAgfSBlbHNlIHsKICAgIHN0b3AoIlRoZSAnc3BlY2llcycgYXJndW1lbnQgbXVzdCBiZSBtb3VzZSBvciBodW1hbiIpCiAgfQogIAogICMgbWFrZSBhIFNldXJhdCBvYmplY3QsIG5vcm1hbGlzZSwgcnVuIHByZWRpY3Rpb24KICAjIG5vdGU6IHdlIHVzZSBTZXVyYXQncyBkZWZhdWx0IG5vcm1hbGlzYXRpb24gdG9vbCBmb3IgdGhlIGNlbGwgcGhhc2UgYXNzZXNzbWVudCAocXVpY2sgYW5kIGRpcnR5KS4gTGF0ZXIgd2Ugd2lsbCB1c2UgU2NyYW4gZm9yIHRoZSBub3JtYWwgbm9ybWFsaXNhdGlvbgogIHNldXJhdE9iaiA8LSBOb3JtYWxpemVEYXRhKHNldXJhdE9iaiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBub3JtYWxpemF0aW9uLm1ldGhvZCA9ICJMb2dOb3JtYWxpemUiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNjYWxlLmZhY3RvciA9IDEwMDAwKQogIHNldXJhdE9iaiA8LSBDZWxsQ3ljbGVTY29yaW5nKHNldXJhdE9iaiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzLmZlYXR1cmVzID0gZ2VuZXNldCRzLmdlbmVzLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGcybS5mZWF0dXJlcyA9IGdlbmVzZXQkZzJtLmdlbmVzLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNldC5pZGVudCA9IFRSVUUpCiAgCiAgIyBkZWZpbmUgc29tZSBncmFwaCBmdW5jdGlvbnMgd2hpY2ggd2lsbCBiZSBydW4gd2l0aCBgdG8ucGRmYCBsYXRlcgogIGZpZy5jZWxsY3ljbGUuYmFyIDwtIGZ1bmN0aW9uKCkgewogICAgbXlzY2FsZSA8LSByb3VuZChtYXgodGFibGUoc2V1cmF0T2JqJFBoYXNlKSksIC0zKSAjc2NhbGUKICAgIG15YmFyIDwtIGJhcnBsb3QodGFibGUoc2V1cmF0T2JqJFBoYXNlKSwKICAgICAgICAgICAgICAgICAgICAgeWxpbSA9IChjKDAsIG15c2NhbGUpKSwKICAgICAgICAgICAgICAgICAgICAgbWFpbiA9IHBhc3RlMCgiQ2VsbCBQaGFzZXMgaW4gIiwgc2FtcGxlSUQpLAogICAgICAgICAgICAgICAgICAgICB4bGFiID0gImNlbGwgcGhhc2UiLAogICAgICAgICAgICAgICAgICAgICB5bGFiID0gIiMgY2VsbHMiLCAKICAgICAgICAgICAgICAgICAgICAgY29sID0gIndoaXRlIikKICAgIHRleHQobXliYXIsCiAgICAgICAgIHRhYmxlKHNldXJhdE9iaiRQaGFzZSkrMTAwLAogICAgICAgICBwYXN0ZSgibjogIiwgdGFibGUoc2V1cmF0T2JqJFBoYXNlKSwgc2VwPSIiKSwgY2V4ID0gMSkgCiAgfQogIAogIGZpZy5jZWxsY3ljbGUucGllIDwtIGZ1bmN0aW9uKCkgewogICAgcGllKHRhYmxlKHNldXJhdE9iaiRQaGFzZSksCiAgICAgICAgbGFiZWxzID0gdGFibGUoc2V1cmF0T2JqJFBoYXNlKSwKICAgICAgICBjb2wgPSBjKCJiaXNxdWUiLCAiY29ybmZsb3dlcmJsdWUiLCAiY2FkZXRibHVlMiIpLAogICAgICAgIG1haW4gPSBwYXN0ZTAoIkNlbGwgcGhhc2VzIGluICIsIHNhbXBsZUlEKSkKICAgIGxlZ2VuZCgidG9wcmlnaHQiLCBjKCJHMSIsICJHMk0iLCAiUyIpLCBjZXggPSAwLjgsIGZpbGwgPSBjKCJiaXNxdWUiLCAiY29ybmZsb3dlcmJsdWUiLCAiY2FkZXRibHVlMiIpKQogIH0KICAKICAjIHNwYXRpYWwgcGxvdHMKICBmaWcuY2VsbGN5Y2xlLnNwYXRpYWwgPC0gZnVuY3Rpb24oKSB7CiAgICBTcGF0aWFsRGltUGxvdChzZXVyYXRPYmosIGdyb3VwLmJ5ID0gIlBoYXNlIikgKwogICAgICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAicmlnaHQiKQogIH0KICAKICAjIFJ1biB0aGUgZmlndXJlIGZ1bmN0aW9ucyBhbmQgc2F2ZSBncmFwaHMgYXMgUERGcwogIHRvLnBkZihmaWcuY2VsbGN5Y2xlLmJhcigpLCBwYXN0ZTAob3V0ZGlyLCBzYW1wbGVJRCwgIl9DZWxsQ3ljbGVfYmFyLnBkZiIpKQogIHRvLnBkZihmaWcuY2VsbGN5Y2xlLnBpZSgpLCBwYXN0ZTAob3V0ZGlyLCBzYW1wbGVJRCwgIl9DZWxsQ3ljbGVfcGllLnBkZiIpKQogIHRvLnBkZihmaWcuY2VsbGN5Y2xlLnNwYXRpYWwoKSwgcGFzdGUwKG91dGRpciwgc2FtcGxlSUQsICJfQ2VsbEN5Y2xlX3NwYXRpYWwucGRmIikpCiAgICAKICAjIHJldHVybiB0aGUgdXBkYXRlZCBTQ0UKICByZXR1cm4oc2V1cmF0T2JqKQp9CiMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiMgTk9STUFMSVNBVElPTgojIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQojIGZ1bmN0aW9uIHRvIG5vcm1hbGlzZSBjb3VudCBkYXRhIGluIHNjcmFuL3NjYXRlcgpmdW5jX3NjcmFuTm9ybSA8LSBmdW5jdGlvbihzZXVyYXRPYmopIHsKICAjIFVTQUdFOiBteVNDRSA8LSBmdW5jX3NjcmFuTm9ybShzZXVyYXRPYmopCiAgIyBPVVRQVVQ6IGEgU0NFIG9iamVjdCB3aXRoIChuYXR1cmFsIGxvZykgbm9ybWFsaXNlZCBjb3VudHMgLSB0aGUgY291bnRzIG5lZWQgdG8gYmUgYWRkZWQgaW50byB0aGUgU2V1cmF0IG9iamVjdCwgYnV0IGluZGlyZWN0bHkgdG8ga2VlcCB0aGUgaW1hZ2UgZGF0YQogICMgTk9URTogdXN1YWxseSwgc2NyYW4gbm9ybWFsaXNhdGlvbiBwcm9kdWNlcyBsb2cyY291bnRzLCBidXQgaGVyZSB3ZSBwcm9kdWNlIHNldXJhdC1jb21wYXRpYmxlIGxuY291bnRzCiAgCiAgIyBjb252ZXJ0IHRvIFNDRSBvYmplY3QKICBteVNDRSA8LSBhcy5TaW5nbGVDZWxsRXhwZXJpbWVudChzZXVyYXRPYmopCiAgIyBjYWxjdWxhdGUgc2l6ZSBmYWN0b3JzIGFuZCBwZXJmb3JtIG5vcm1hbGlzYXRpb24KICBzY3JhbmNsdXN0ZXJzIDwtIHF1aWNrQ2x1c3RlcihteVNDRSkKICBteVNDRSA8LSBjb21wdXRlU3VtRmFjdG9ycyhteVNDRSwgY2x1c3RlcnMgPSBzY3JhbmNsdXN0ZXJzKQogICMgInNjcmFuIHNvbWV0aW1lcyBjYWxjdWxhdGVzIG5lZ2F0aXZlIG9yIHplcm8gc2l6ZSBmYWN0b3JzIHdoaWNoIHdpbGwgY29tcGxldGVseSBkaXN0b3J0IHRoZSBub3JtYWxpemVkIGV4cHJlc3Npb24gbWF0cml4Ii4gTGV0J3MgY2hlY2sKICBtaW5zaXplZmFjdG9yIDwtIG1pbihzaXplRmFjdG9ycyhteVNDRSkpCiAgaWYgKG1pbnNpemVmYWN0b3IgPCAwKSB7CiAgICB3YXJuaW5nKCJBTEVSVCEgc2NyYW4gbm9ybWFsaXNhdGlvbiBoYXMgcHJvZHVjZWQgbmVnYXRpdmUgb3IgemVybyBzaXplIGZhY3RvcnMgd2hpY2ggd2lsbCBkaXN0b3J0IHRoZSBub3JtYWxpc2VkIGV4cHJlc3Npb24gbWF0cml4LiBQcm9jZWVkIHdpdGggY2FyZSFcbiBZb3UgY2FuIHRyeSBpbmNyZWFzaW5nIHRoZSBjbHVzdGVyIGFuZCBwb29sIHNpemVzIHVudGlsIHRoZXkgYXJlIGFsbCBwb3NpdGl2ZVxuIFNlZSBodHRwczovL2Jpb2NlbGxnZW4tcHVibGljLnN2aS5lZHUuYXUvbWlnXzIwMTlfc2NybmFzZXEtd29ya3Nob3AvcHVibGljL25vcm1hbGl6YXRpb24tY29uZm91bmRlcnMtYW5kLWJhdGNoLWNvcnJlY3Rpb24uaHRtbCIpCiAgfQogIG15U0NFIDwtIHNjYXRlcjo6bG9nTm9ybUNvdW50cyhteVNDRSwgbG9nID0gRkFMU0UsIG5hbWUgPSAidW5sb2cubm9ybWNvdW50cyIpCiAgCiAgIyBuYXR1cmFsLWxvZyB0cmFuc2Zvcm0gY291bnRzIGFuZCBjb252ZXJ0IGJhY2sgdG8gc3BhcnNlIG1hdHJpeCBmb3JtYXQKICBhc3NheShteVNDRSwgImxuLm5vcm1jb3VudHMiKSA8LSBhcyhsb2coeCA9IGFzc2F5KG15U0NFLCAidW5sb2cubm9ybWNvdW50cyIpICsgMSksICJkZ0NNYXRyaXgiKQogIHJldHVybihteVNDRSkKICAjIE5PVEU6IFRvIGNvbnZlcnQgdG8gU2V1cmF0IG9iamVjdCBmcm9tIG5vdyBvbiB5b3VyIG11c3QgcnVuOgogICMgc2V1cmF0T2JqIDwtIGFzLlNldXJhdChteVNDRSwgY291bnRzID0gImNvdW50cyIsIGRhdGEgPSAibG4ubm9ybWNvdW50cyIpCn0KYGBgCgpgYGB7cn0KZnVuY19TY2FsZURhdGEgPC0gZnVuY3Rpb24oc2V1cmF0T2JqKSB7CiAgIyBVU0FHRTogc2V1cmF0T2JqIDwtIGZ1bmNfU2NhbGVEYXRhKG15U0NFKQogICMgT1VUUFVUOiBhIFNldXJhdCBvYmplY3Qgd2l0aCBzY2FsZWQgbm9ybWFsaXNlZCBjb3VudHMKICAjIGZpbmQgdmFyaWFibGUgZmVhdHVyZXMsIHBlcmZvcm0gc2NhbGluZwogIHNldXJhdE9iaiA8LSBGaW5kVmFyaWFibGVGZWF0dXJlcyhzZXVyYXRPYmosIHNlbGVjdGlvbi5tZXRob2QgPSAidnN0IiwgbmZlYXR1cmVzID0gMjAwMCkKICBzZXVyYXRPYmogPC0gU2NhbGVEYXRhKHNldXJhdE9iaikKICAKICAjIGNvbnZlcnQgdG8gU0NFIG9iamVjdAogICNteVNDRSA8LSBhcy5TaW5nbGVDZWxsRXhwZXJpbWVudChzZXVyYXRPYmopCiAgIyBhbHRlcm5hdGl2ZWx5LCBkb24ndCBjb252ZXJ0IGJhY2sgZnJlc2gsIGp1c3QgaW5zZXJ0IHRoZSBzY2FsZWRhdGEgYXMgYSBuZXcgYXNzYXkgdHlwZQogICNyZXR1cm4obXlTQ0UpCiAgCiAgIyBmb3Igbm93LCBqdXN0IHJldHVybiB0aGUgU2V1cmF0IG9iamVjdAogIHJldHVybihzZXVyYXRPYmopCn0KIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KIyBQQ0EKIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KIyBmdW5jdGlvbiB0byBmaW5kIHZhcmlhYmxlIGZlYXR1cmVzIGFuZCBzY2FsZSBkYXRhIHVzaW5nIFNldXJhdApmdW5jX3J1blBDQSA8LSBmdW5jdGlvbihzZXVyYXRPYmosIHJ1bkphY2tzdHJhdyA9ICJUUlVFIikgewogICMgVVNBR0U6IHNldXJhdE9iaiA8LSBmdW5jX3J1blBDQShzZXVyYXRPYmosIHJ1bkphY2tzdHJhdyA9ICJUUlVFIiBvciAiRkFMU0UiKQogICMgT1VUUFVUOiBhIFNldXJhdCBvYmplY3Qgd2l0aCBQQ0EgcnVuCiAgCiAgIyBSdW4gUENBCiAgc2V1cmF0T2JqIDwtIFJ1blBDQShzZXVyYXRPYmosIGZlYXR1cmVzID0gVmFyaWFibGVGZWF0dXJlcyhvYmplY3QgPSBzZXVyYXRPYmopLCBucGNzID0gNTApCiAgCiAgIyBjYWxjdWxhdGUgdmFyaWFuY2UgZXhwbGFpbmVkIGJ5IGVhY2ggUEMKICB0b3RhbF92YXJpYW5jZSA8LSBzZXVyYXRPYmpAcmVkdWN0aW9ucyRwY2FAbWlzYyR0b3RhbC52YXJpYW5jZQogIGVpZ1ZhbHVlcyA8LSAoc2V1cmF0T2JqW1sicGNhIl1dQHN0ZGV2KV4yCiAgdmFyRXhwbGFpbmVkIDwtIGVpZ1ZhbHVlcyAvIHRvdGFsX3ZhcmlhbmNlCiAgdmFyRXhwbGFpbmVkLmN1bSA8LSBjdW1zdW0odmFyRXhwbGFpbmVkKQogICMjIyBob3cgbWFueSBQQ3MgYmVmb3JlIDIwICUgb2YgdGhlIHZhcmlhbmNlIGlzIGV4cGxhaW5lZD8KICB2YXIuMjBwYyA8LSBzdW0odmFyRXhwbGFpbmVkLmN1bSA8PSAwLjIpCiAgIyMjIGhvdyBtdWNoIHZhcmlhbmNlIGRvIDUwIFBDcyBleHBsYWluPwogIHZhcnBjLjUwUENBIDwtIDEwMCoodmFyRXhwbGFpbmVkLmN1bVs1MF0pCiAgcHJpbnQocGFzdGUwKCJUaGUgZmlyc3QgNTAgUENzIGV4cGxhaW4gIiwgcm91bmQodmFycGMuNTBQQ0EpLCAiJSBvZiB0aGUgdmFyaWFuY2UuIDIwJSBvZiB0aGUgdmFyaWFuY2UgaXMgZXhwbGFpbmVkIGJ5IHRoZSBmaXJzdCAiLCB2YXIuMjBwYywgIiBQQ3MiKSkKICAKICAjIGRlZmluZSBzb21lIGdyYXBoIGZ1bmN0aW9ucyB3aGljaCB3aWxsIGJlIHJ1biB3aXRoIGB0by5wZGZgIGxhdGVyCiAgIyMgc2NyZWUgcGxvdAogIGZpZy5zY3JlZSA8LSBmdW5jdGlvbigpIHsKICAgIHZhckV4cGxhaW5lZCAlPiUgZW5mcmFtZShuYW1lID0gIlBDIiwgdmFsdWUgPSAidmFyRXhwbGFpbmVkIiApICU+JQogICAgICBnZ3Bsb3QoYWVzKHggPSBQQywgeSA9IHZhckV4cGxhaW5lZCkpICsgCiAgICAgIHRoZW1lX2J3KCkgKwogICAgICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IikgKwogICAgICB0aGVtZV9jbGFzc2ljKCkgKwogICAgICBnZ3RpdGxlKHBhc3RlMChzYW1wbGVJRCwgIjogc2NyZWUgcGxvdCIpKSArCiAgICAgIHlsYWIoImV4cGxhaW5lZCB2YXJpYW5jZSIpCiAgfQogICMjIGN1bXVsYXRpdmUgdmFyaWFuY2UKICBmaWcuY3VtdWxhdGl2ZVZhciA8LSBmdW5jdGlvbigpIHsKICAgIGdncGxvdChhcy5kYXRhLmZyYW1lKHZhckV4cGxhaW5lZC5jdW0pLCBhZXMoeSA9IHZhckV4cGxhaW5lZC5jdW0sIHggPSBzZXEoMSwgbGVuZ3RoKHZhckV4cGxhaW5lZC5jdW0pKSkpICsKICAgICAgZ2VvbV9wb2ludChzaXplID0gMSkgKwogICAgICB0aGVtZV9idygpICsKICAgICAgZ2d0aXRsZSgiY3VtdWxhdGl2ZSB2YXJpYW5jZSBleHBsYWluZWQgYnkgaW5jcmVhc2luZyBQQ3MiKSArCiAgICAgIHhsYWIoIlBDcyIpICsKICAgICAgeWxhYigiY3VtdWxhdGl2ZSBleHBsYWluZWQgdmFyaWFuY2UiKSArCiAgICAgIGdlb21faGxpbmUoeWludGVyY2VwdCA9IGMoMC4yKSwgbGluZXR5cGUgPSAiZGFzaGVkIiwgY29sb3IgPSAiYmx1ZSIpICsKICAgICAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gYygyMCksIGxpbmV0eXBlID0gImRhc2hlZCIsIGNvbG9yID0gImJsdWUiKQogIH0KICAKICAjIE1ha2UgYW4gZWxib3cgcGxvdCB3aXRoIGVsYm93IHBvaW50IGFubm90YXRlZCAoYWRhcHRlZCBmcm9tIFNldXJhdCdzIEVsYm93UGxvdCgpIGJ1dCB0byBzaG93IGFsbCB0ZXN0ZWQgUENzKQogIGZpZy5lbGJvdyA8LSBmdW5jdGlvbigpIHsKICAgIEVsYm93UGxvdChzZXVyYXRPYmosIG5kaW1zID0gNTAsIHJlZHVjdGlvbiA9ICJwY2EiKSArCiAgICAgIHRoZW1lX2J3KCkgKwogICAgICBnZ3RpdGxlKHBhc3RlMChzYW1wbGVJRCwgIjogZWxib3cgcGxvdCBvZiBzdGFuZGFyZCBkZXZpYXRpb25zIG9mIHByaW5jaXBhbCBjb21wb25lbnRzIikpCiAgfQogIAogICMgUGVyZm9ybSBKYWNrU3RyYXcgYW5hbHlzaXMKICBpZiAocnVuSmFja3N0cmF3ID09ICJUUlVFIikgewogICAgc2V1cmF0T2JqIDwtIEphY2tTdHJhdyhzZXVyYXRPYmosIG51bS5yZXBsaWNhdGUgPSAxMDAsIGRpbXMgPSA1MCkKICAgIHNldXJhdE9iaiA8LSBTY29yZUphY2tTdHJhdyhzZXVyYXRPYmosIGRpbXMgPSAxOjUwKSAjIGJlY2F1c2UgYFJ1blBDQWAgY2FsY3VsYXRlcyA1MHggUENzIGJ5IGRlZmFsdCAoeW91IGNhbiBjaGFuZ2UgdGhpcykKICAgIGZpZy5qYWNrc3RyYXcgPC0gZnVuY3Rpb24oKSB7CiAgICAgIEphY2tTdHJhd1Bsb3Qoc2V1cmF0T2JqLCBkaW1zID0gMTo1MCkgKwogICAgICAgIGdndGl0bGUoIlBDQSBKYWNrU3RyYXciKQogICAgfQogICAgIyB0aGUgUEMgcC12YWxzIGFyZSBpbiBzZXVyYXRPYmpAcmVkdWN0aW9ucyRwY2FAamFja3N0cmF3JG92ZXJhbGwucC52YWx1ZXMKICAgICMgZ2V0IHRoZSBQQyBudW1iZXIgb2YgdGhlIGxhc3QgUEMgYmVmb3JlIG9uZSBpcyBub3Qgc2lnbmlmaWNhbnQKICAgIGpzY29yZXMgPC0gYXMuZGF0YS5mcmFtZShzZXVyYXRPYmpAcmVkdWN0aW9ucyRwY2FAamFja3N0cmF3JG92ZXJhbGwucC52YWx1ZXMgPiAwLjA1KQogICAgY2hvc2VuLmphY2sgPC0gYXMubnVtZXJpYyhyb3duYW1lcyhqc2NvcmVzW2pzY29yZXMkU2NvcmUgPT0gIlRSVUUiLCBdWzEsXSkpIC0gMQogICAgdG8ucGRmKGZpZy5qYWNrc3RyYXcoKSwgcGFzdGUwKG91dGRpciwgc2FtcGxlSUQsICJfUENBX2phY2tzdHJhdy5wZGYiKSkKICAgIH0gZWxzZSB7CiAgICAgIGlmIChydW5KYWNrc3RyYXcgPT0gIkZBTFNFIikgewogICAgICBwcmludCgic2tpcHBpbmcgSmFja3N0cmF3IGFuYWx5c2lzIikKICAgIH0gZWxzZSB7CiAgICAgIHN0b3AoInJ1bkphY2tzdHJhdyBtdXN0IGJlIFRSVUUgb3IgRkFMU0UiKQogICAgfQogIH0KICAKICAjIFJ1biB0aGUgZmlndXJlIGZ1bmN0aW9ucyBhbmQgc2F2ZSBncmFwaHMgYXMgUERGcwogIHRvLnBkZihmaWcuc2NyZWUoKSwgcGFzdGUwKG91dGRpciwgc2FtcGxlSUQsICJfc2NyZWUucGRmIikpCiAgdG8ucGRmKGZpZy5jdW11bGF0aXZlVmFyKCksIHBhc3RlMChvdXRkaXIsIHNhbXBsZUlELCAiX2N1bXVsYXRpdmVWYXJpYW5jZS5wZGYiKSkKICB0by5wZGYoZmlnLmVsYm93KCksIHBhc3RlMChvdXRkaXIsIHNhbXBsZUlELCAiX1BDQV9lbGJvdy5wZGYiKSkKICAjIHRvLnBkZihmaWcuamFja3N0cmF3KCksIHBhc3RlMChvdXRkaXIsICJmaWdzLyIsIHNhbXBsZUlELCAiX1BDQV9qYWNrc3RyYXcucGRmIikpICNydW4gYWJvdmUgaW4gaWYvZWxzZSBiaXQKICAjIGZvciBub3csIGp1c3QgcmV0dXJuIHRoZSBTZXVyYXQgb2JqZWN0CiAgcmV0dXJuKHNldXJhdE9iaikKfQpmdW5jX3J1bk5vbkxpbmVhckRSIDwtIGZ1bmN0aW9uKHNldXJhdE9iaiwgcnVuVFNORSA9ICJUUlVFIikgewogICMgVVNBR0U6IHNldXJhdE9iaiA8LSBmdW5jX3J1bk5vbkxpbmVhckRSKHNldXJhdE9iaiwgcnVuVFNORSA9ICJUUlVFIiBvciAiRkFMU0UiKQogICMgT1VUUFVUOiBhIFNldXJhdCBvYmplY3Qgd2l0aCB0U05FIGFuZCBVTUFQIGNvb3JkaW5hdGVzCiAgCiAgIyBSdW4gVU1BUAogIHNldXJhdE9iaiA8LSBTZXVyYXQ6OlJ1blVNQVAoc2V1cmF0T2JqLCBkaW1zID0gMToyMCwgbi5uZWlnaGJvcnMgPSA1LCBtaW4uZGlzdCA9IDAuMSkKICBmaWcudW1hcC5yYXcgPC0gZnVuY3Rpb24oKSB7CiAgICAjIGFsdGVybmF0aXZlIHRvIERpbVBsb3Qoc2V1cmF0T2JqLCByZWR1Y3Rpb24gPSAidW1hcCIpCiAgICBFbWJlZGRpbmdzKHNldXJhdE9iaiwgcmVkdWN0aW9uID0gInVtYXAiKSAlPiUKICAgICAgYXMuZGF0YS5mcmFtZSgpICU+JQogICAgICBnZ3Bsb3QoYWVzKHggPSBVTUFQXzEsIHkgPSBVTUFQXzIpKSArCiAgICAgIGdlb21fcG9pbnQoc2l6ZSA9IDAuMykgKwogICAgICB0aGVtZV9idyhiYXNlX3NpemUgPSAxNCkgKwogICAgICBnZ3RpdGxlKHBhc3RlMChzYW1wbGVJRCwgIjogVU1BUCIpKQogIH0KICB0by5wZGYoZmlnLnVtYXAucmF3KCksIHBhc3RlMChvdXRkaXIsIHNhbXBsZUlELCAiX1VNQVBfcmF3LnBkZiIpKQogIAogICMgUnVuIHRTTkUKICBpZiAocnVuVFNORSA9PSAiVFJVRSIpIHsKICAgIHNldXJhdE9iaiA8LSBTZXVyYXQ6OlJ1blRTTkUoc2V1cmF0T2JqLCBkaW1zID0gMTo1MCkKICAgIAogICAgZmlnLnRTTkUucmF3IDwtIGZ1bmN0aW9uKCkgewogICAgICAjIGFsdGVybmF0aXZlIHRvIERpbVBsb3Qoc2V1cmF0T2JqLCByZWR1Y3Rpb24gPSAidFNORSIpCiAgICAgIEVtYmVkZGluZ3Moc2V1cmF0T2JqLCByZWR1Y3Rpb24gPSAidHNuZSIpICU+JQogICAgICAgIGFzLmRhdGEuZnJhbWUoKSAlPiUKICAgICAgICBnZ3Bsb3QoYWVzKHggPSB0U05FXzEsIHkgPSB0U05FXzIpKSArCiAgICAgICAgZ2VvbV9wb2ludChzaXplID0gMC4zKSArCiAgICAgICAgdGhlbWVfYncoYmFzZV9zaXplID0gMTQpICsKICAgICAgICBnZ3RpdGxlKHBhc3RlMChzYW1wbGVJRCwgIjogdFNORSIpKQogICAgfQogICAgdG8ucGRmKGZpZy50U05FLnJhdygpLCBwYXN0ZTAob3V0ZGlyLCBzYW1wbGVJRCwgIl90U05FX3Jhdy5wZGYiKSkKICB9IGVsc2UgewogICAgaWYgKHJ1blRTTkUgPT0gIkZBTFNFIikgewogICAgICBwcmludCgic2tpcHBpbmcgdFNORSBwbG90IikKICAgIH0gZWxzZSB7CiAgICAgIHN0b3AoInJ1blRTTkUgbXVzdCBiZSBUUlVFIG9yIEZBTFNFIikKICAgIH0KICB9CiAgcmV0dXJuKHNldXJhdE9iaikKfQpgYGAKCiMgUUMgMSAtIFJFTU9WRSBMT1cgQ09VTlRTCgpgYGB7cn0KIyBsb29rIGF0IHRoZSBudW1iZXIgb2YgZmVhdHVyZXMgYW5kIGNlbGxzCmRmCmBgYAoKTm93IHdlIHdpbGwgbG9vayBhdCBRQyBwbG90cy4gVGhlIGBuRmVhdHVyZWAgbWVhc3VyZSBzaG93cyB1cyB0aGUgbnVtYmVyIG9mIGdlbmVzIHBlciBzcG90LCB3aGlsZSBgbkNvdW50YCByZWZlcnMgdG8gdGhlIG51bWJlciBvZiBSTkEgdHJhbnNjcmlwdHMgKGkuZS4gdG90YWwgY291bnRzKSBwZXIgc3BvdC4gV2UgY2FuIGVpdGhlciB2aXN1YWxpc2UgdGhlc2UgbWVhc3VyZXMgb24gdGhlaXIgb3duIGFzIHZpb2xpbiBwbG90cywgb3Igd2UgY2FuIHBsb3QgdGhlbSB0b2dldGhlciBvbiBhIHNjYXR0ZXIgcGxvdCwgd2hlcmUgd2UgZXhwZWN0IHRoZSB0cmVuZCB0byBiZSByb3VnaGx5IGRpYWdvbmFsLiBJZiB3ZSBzZWUgb3V0bGllcnMgZnJvbSB0aGlzIGRpYWdvbmFsLCB0aGV5IGFyZSBpbmRpY2F0aXZlIG9mIHdlaXJkIHNwb3RzLgoKYGBge3IgZmlnLndpZHRoPTEyfQojIExvb2sgYXQgc29tZSBRQyBwbG90cwpWbG5QbG90KGRmLCBmZWF0dXJlcyA9IGMoIm5GZWF0dXJlX1NwYXRpYWwiLCAibkNvdW50X1NwYXRpYWwiKSwgZ3JvdXAuYnkgPSAib3JpZy5pZGVudCIpCmdnc2F2ZShwYXN0ZTAob3V0ZGlyLCBzYW1wbGVJRCwgIl9jb3VudHNBbmRGZWF0dXJlcy5wZGYiKSkKRmVhdHVyZVNjYXR0ZXIoZGYsIGZlYXR1cmUxID0gIm5GZWF0dXJlX1NwYXRpYWwiLCBmZWF0dXJlMiA9ICJuQ291bnRfU3BhdGlhbCIsIGdyb3VwLmJ5ID0gIm9yaWcuaWRlbnQiKSArIE5vTGVnZW5kKCkKZ2dzYXZlKHBhc3RlMChvdXRkaXIsIHNhbXBsZUlELCAiX3NjYXR0ZXIucGRmIikpCmBgYAoKRmlsdGVyIG91dCBzcG90cyB3aXRoIGxvdyBjb3VudHMgYW5kIGZlYXR1cmVzIChyZXF1aXJlcyBhdCBsZWFzdCAxMDAgY291bnRzIGFuZCAxMDAgZmVhdHVyZSBwZXIgc3BvdCkKCmBgYHtyfQpkZiA8LSBzdWJzZXQoZGYsIHN1YnNldCA9IG5Db3VudF9TcGF0aWFsID4gMTAwICYgbkZlYXR1cmVfU3BhdGlhbCA+IDEwMCkKZGYKYGBgCgojIFFDIDIgLSBNSVRPQ0hPTkRSSUEgQU5EIFJJQk9TT01FUwoKTm93IHdlJ2xsIGxvb2sgZm9yIHNwb3RzIHdpdGggZXhjZXNzaXZlbHkgaGlnaCBwZXJjZW50YWdlcyBvZiByaWJvc29tYWwgb3IgbWl0b2Nob25kcmlhbCBnZW5lcywgd2hpY2ggbWF5IGZ1cnRoZXIgaW5kaWNhdGUgYSBxdWFsaXR5IHByb2JsZW0uIFdlJ3JlIGFyYml0cmFyaWx5IGdvaW5nIHRvIGZpbHRlciBzcG90cyB3aXRoID41MCUgbWl0b2Nob25kcmlhbCBnZW5lcyBhbmQvb3IgPjUwJSByaWJvc29tYWwgZ2VuZXMuIFRvIHNlZSB0aGUgImJlZm9yZSBmaWx0ZXJpbmciIHBsb3RzLCBoYXZlIGEgbG9vayBhdCB0aGUgYWNjb21wYW55aW5nIHBsb3RzIGRpcmVjdG9yeS4KCmBgYHtyfQpkZiA8LSBmdW5jX2Fzc2Vzc01ULlJUKGRmLCBzYW1wbGVJRCkKYGBgCgpgYGB7ciBmaWcud2lkdGg9OH0KIyB0aGlzIGlzIHdoYXQgdGhlIGRhdGEgbG9vayBsaWtlLCBwb3N0LWZpbHRlcmluZwpTcGF0aWFsRmVhdHVyZVBsb3QoZGYsIGZlYXR1cmVzID0gInBlcmNlbnQubXQiKSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJyaWdodCIpCmdnc2F2ZShwYXN0ZTAob3V0ZGlyLCBzYW1wbGVJRCwgIl9wZXJjZW50TWl0b2Nob25kcmlhU3BhdGlhbF9maWx0ZXJlZC5wZGYiKSkKU3BhdGlhbEZlYXR1cmVQbG90KGRmLCBmZWF0dXJlcyA9ICJwZXJjZW50LnJiIikgKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAicmlnaHQiKQpnZ3NhdmUocGFzdGUwKG91dGRpciwgc2FtcGxlSUQsICJfcGVyY2VudFJpYm9zb21lU3BhdGlhbF9maWx0ZXJlZC5wZGYiKSkKYGBgCgpBbmQganVzdCBjaGVjayBpbiBvbiBob3cgbWFueSBnZW5lcy9jZWxscyByZW1haW4gaW4gb3VyIGRhdGFzZXQ6CgpgYGB7cn0KZGYKYGBgCgojIFFDIDMgLSBDRUxMIENZQ0xFIEFOTk9UQVRJT04KCk5vdyB3ZSB3aWxsIGRvIGEgY2VsbCBjeWNsZSBwcmVkaWN0aW9uLiBUaGlzIG1ldGhvZCBsb29rcyBhdCBjZXJ0YWluIG1hcmtlciBnZW5lcyBhc3NvY2lhdGVkIHdpdGggZGlmZmVyZW50IHBoYXNlcyBvZiBtaXRvc2lzLCBhbmQgaXMgZGVzY3JpYmVkIGluIGEgU2V1cmF0IHZpZ25ldHRlLiBUaGlzIHByZWRpY3Rpb24gaXMgdHlwaWNhbGx5IHVzZWQgZm9yIHNpbmdsZSBjZWxsIGRhdGEsIHNvIGl0J3MgcG9zc2libGUgaXQgd29uJ3QgcGVyZm9ybSBhcyB3ZWxsIGhlcmUgd2l0aCBTVCBkYXRhLiBGb3IgbW9yZSBpbmZvcm1hdGlvbiBhYm91dCB0aGlzIGFuYWx5c2lzLCBzZWUgdGhlIFtTZXVyYXQgdmlnbmV0dGVdKGh0dHBzOi8vc2F0aWphbGFiLm9yZy9zZXVyYXQvdjMuMS9jZWxsX2N5Y2xlX3ZpZ25ldHRlLmh0bWwpLiBIb3dldmVyLCB1bmxpa2UgaW4gdGhlIFNldXJhdCB2aWduZXR0ZSwgd2UgYXJlbid0IGdvaW5nIHRvIGluY2x1ZGUgdGhpcyBkYXRhIGluIGFueSByZWdyZXNzaW9uIHN0ZXBzIC0gd2UgYXJlIGp1c3QgaW50ZXJlc3RlZCBpbiBzZWVpbmcgdGhlIHRyZW5kcyBhY3Jvc3Mgb3VyIHRpc3N1ZS4KCmBgYHtyfQpkZiA8LSBmdW5jX3ByZWRpY3RDZWxsQ3ljbGUoZGYsICJodW1hbiIpCmBgYAoKTGV0J3MgdmlzdWFsaXNlIHRoZSByZXN1bHRzLiBXZSdsbCBncmV5IG91dCB0aGUgRzEtcGhhc2Ugc3BvdHMgc28gd2UgaGlnaGxpZ2h0IHRob3NlIHRoYXQgYXJlIGRpdmlkaW5nLgoKYGBge3IgZmlnLmhlaWdodD0xMCwgZmlnLndpZHRoPTEwfQpTcGF0aWFsRGltUGxvdChkZiwgZ3JvdXAuYnkgPSAiUGhhc2UiKSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJyaWdodCIpICsgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYygiZ3JleSIsICIjRjRBNjk4IiwgIiNERDYxNEEiLCAiYmxhY2siKSkKZ2dzYXZlKHBhc3RlMChvdXRkaXIsIHNhbXBsZUlELCAiX0NlbGxDeWNsZV9zcGF0aWFsUHJldHR5LnBkZiIpKQpgYGAKCiMgTk9STUFMSVNBVElPTiBJTiBTQ1JBTgoKSGVyZSB3ZSBkaXZlcmdlIGZyb20gdGhlIFNldXJhdCBwaXBlbGluZSB0byBydW4gU2NyYW4gbm9ybWFsaXNhdGlvbiBpbnN0ZWFkLgoKYGBge3J9CmRmLnNjZSA8LSBmdW5jX3NjcmFuTm9ybShkZikKZGYudGVtcCA8LSBhcy5TZXVyYXQoZGYuc2NlLCBjb3VudHMgPSAiY291bnRzIiwgZGF0YSA9ICJsbi5ub3JtY291bnRzIikKZGZAYXNzYXlzJFNwYXRpYWxAY291bnRzIDwtIGRmLnRlbXBAYXNzYXlzJFJOQUBjb3VudHMKZGZAYXNzYXlzJFNwYXRpYWxAZGF0YSA8LSBkZi50ZW1wQGFzc2F5cyRSTkFAZGF0YQpgYGAKCiMgUlVOIFBDQSBBTkQgVU1BUAoKYGBge3J9CmRmIDwtIGZ1bmNfU2NhbGVEYXRhKGRmKQpkZiA8LSBmdW5jX3J1blBDQShkZiwgcnVuSmFja3N0cmF3ID0gIkZBTFNFIikKRWxib3dQbG90KGRmLCBuZGltcyA9IDUwKQpnZ3NhdmUocGFzdGUwKG91dGRpciwgc2FtcGxlSUQsICJfZWxib3dwbG90LmpwZWciKSkKZGYgPC0gZnVuY19ydW5Ob25MaW5lYXJEUihkZiwgcnVuVFNORSA9ICJUUlVFIikKZGYgPC0gRmluZE5laWdoYm9ycyhkZiwgcmVkdWN0aW9uID0gInBjYSIsIGRpbXMgPSAxOjIwKQpgYGAKCmBgYHtyfQpEaW1QbG90KGRmLCByZWR1Y3Rpb24gPSAidW1hcCIsIGdyb3VwLmJ5ID0gIm9yaWcuaWRlbnQiKQpEaW1QbG90KGRmLCByZWR1Y3Rpb24gPSAidHNuZSIsIGdyb3VwLmJ5ID0gIm9yaWcuaWRlbnQiKQpgYGAKCiMgQ0xVU1RFUiBBTkQgVEVTVCBXSVRIIENMVVNUUkVFCgpOb3cgd2UncmUgZ29pbmcgdG8gY2x1c3RlciB0aGUgY2VsbHMuIEZpcnN0LCB3ZSdsbCBtYWtlIGEgdGVtcG9yYXJ5IFIgb2JqZWN0IGBkZi4yYCBhbmQgdGVzdCBhIHJhbmdlIG9mIGRpZmZlcmVudCByZXNvbHV0aW9uIHZhbHVlcy4gVGhlIHJlc29sdXRpb24gcGFyYW1ldGVyICJjb250cm9sW3NdIHRoZSBzaXplIGFuZCBzdHJ1Y3R1cmUgb2YgY29tbXVuaXRpZXMgdGhhdCBhcmUgZm9ybWVkIGJ5IG9wdGltaXppbmcgYSBnZW5lcmFsaXplZCBvYmplY3RpdmUgZnVuY3Rpb24iLiBFZmZlY3RpdmVseSwgYW4gaW5jcmVhc2VkIHJlc29sdXRpb24gPSBtb3JlIGNsdXN0ZXJzIC0gdGhvdWdoIHlvdSBjYW4ndCB0ZWxsIFNldXJhdCB0byBnaXZlIHlvdSBleGFjdGx5IE4gY2x1c3RlcnMsIGFuZCBvZnRlbiBkaWZmZXJlbnQgcmVzb2x1dGlvbiB2YWx1ZXMgd2lsbCBnaXZlIHRoZSBzYW1lIG51bWJlciBvZiBjbHVzdGVycy4KCmBgYHtyIGluY2x1ZGU9RkFMU0V9CiMgSSBhbHJlYWR5IHJhbiBmaW5kTmVpZ2hib3VycyBhYm92ZQpkZi4yIDwtIEZpbmRDbHVzdGVycyhkZiwgcmVzb2x1dGlvbiA9IDApCmRmLjIgPC0gRmluZENsdXN0ZXJzKGRmLjIsIHJlc29sdXRpb24gPSAwLjEpCmRmLjIgPC0gRmluZENsdXN0ZXJzKGRmLjIsIHJlc29sdXRpb24gPSAwLjIpCmRmLjIgPC0gRmluZENsdXN0ZXJzKGRmLjIsIHJlc29sdXRpb24gPSAwLjMpCmRmLjIgPC0gRmluZENsdXN0ZXJzKGRmLjIsIHJlc29sdXRpb24gPSAwLjQpCmRmLjIgPC0gRmluZENsdXN0ZXJzKGRmLjIsIHJlc29sdXRpb24gPSAwLjYpCmRmLjIgPC0gRmluZENsdXN0ZXJzKGRmLjIsIHJlc29sdXRpb24gPSAwLjgpCmRmLjIgPC0gRmluZENsdXN0ZXJzKGRmLjIsIHJlc29sdXRpb24gPSAxKQpkZi4yIDwtIEZpbmRDbHVzdGVycyhkZi4yLCByZXNvbHV0aW9uID0gMS4yKQpkZi4yIDwtIEZpbmRDbHVzdGVycyhkZi4yLCByZXNvbHV0aW9uID0gMS40KQpkZi4yIDwtIEZpbmRDbHVzdGVycyhkZi4yLCByZXNvbHV0aW9uID0gMS42KQpgYGAKCkxvb2sgYXQgdGhlIHBsb3RzCgpgYGB7cn0KRGltUGxvdChkZi4yLCByZWR1Y3Rpb24gPSAidW1hcCIsIGdyb3VwLmJ5ID0gIlNwYXRpYWxfc25uX3Jlcy4wIikgKyBnZ3RpdGxlKCJyZXMgPSAwIikKRGltUGxvdChkZi4yLCByZWR1Y3Rpb24gPSAidW1hcCIsIGdyb3VwLmJ5ID0gIlNwYXRpYWxfc25uX3Jlcy4wLjEiKSArIGdndGl0bGUoInJlcyA9IDAuMSIpCkRpbVBsb3QoZGYuMiwgcmVkdWN0aW9uID0gInVtYXAiLCBncm91cC5ieSA9ICJTcGF0aWFsX3Nubl9yZXMuMC4yIikgKyBnZ3RpdGxlKCJyZXMgPSAwLjIiKQpEaW1QbG90KGRmLjIsIHJlZHVjdGlvbiA9ICJ1bWFwIiwgZ3JvdXAuYnkgPSAiU3BhdGlhbF9zbm5fcmVzLjAuMyIpICsgZ2d0aXRsZSgicmVzID0gMC4zIikKRGltUGxvdChkZi4yLCByZWR1Y3Rpb24gPSAidW1hcCIsIGdyb3VwLmJ5ID0gIlNwYXRpYWxfc25uX3Jlcy4wLjQiKSArIGdndGl0bGUoInJlcyA9IDAuNCIpCkRpbVBsb3QoZGYuMiwgcmVkdWN0aW9uID0gInVtYXAiLCBncm91cC5ieSA9ICJTcGF0aWFsX3Nubl9yZXMuMC42IikgKyBnZ3RpdGxlKCJyZXMgPSAwLjYiKQpEaW1QbG90KGRmLjIsIHJlZHVjdGlvbiA9ICJ1bWFwIiwgZ3JvdXAuYnkgPSAiU3BhdGlhbF9zbm5fcmVzLjAuOCIpICsgZ2d0aXRsZSgicmVzID0gMC44IikKRGltUGxvdChkZi4yLCByZWR1Y3Rpb24gPSAidW1hcCIsIGdyb3VwLmJ5ID0gIlNwYXRpYWxfc25uX3Jlcy4xIikgKyBnZ3RpdGxlKCJyZXMgPSAxIikKRGltUGxvdChkZi4yLCByZWR1Y3Rpb24gPSAidW1hcCIsIGdyb3VwLmJ5ID0gIlNwYXRpYWxfc25uX3Jlcy4xLjIiKSArIGdndGl0bGUoInJlcyA9IDEuMiIpCkRpbVBsb3QoZGYuMiwgcmVkdWN0aW9uID0gInVtYXAiLCBncm91cC5ieSA9ICJTcGF0aWFsX3Nubl9yZXMuMS40IikgKyBnZ3RpdGxlKCJyZXMgPSAxLjQiKQpEaW1QbG90KGRmLjIsIHJlZHVjdGlvbiA9ICJ1bWFwIiwgZ3JvdXAuYnkgPSAiU3BhdGlhbF9zbm5fcmVzLjEuNiIpICsgZ2d0aXRsZSgicmVzID0gMS42IikKYGBgCgpgYGB7ciBmaWcud2lkdGg9MTJ9ClNwYXRpYWxEaW1QbG90KGRmLjIsIGdyb3VwLmJ5ID0gIlNwYXRpYWxfc25uX3Jlcy4wIikgKyBnZ3RpdGxlKCJyZXMgPSAwIikKU3BhdGlhbERpbVBsb3QoZGYuMiwgZ3JvdXAuYnkgPSAiU3BhdGlhbF9zbm5fcmVzLjAuMSIsKSArIGdndGl0bGUoInJlcyA9IDAuMSIpClNwYXRpYWxEaW1QbG90KGRmLjIsIGdyb3VwLmJ5ID0gIlNwYXRpYWxfc25uX3Jlcy4wLjIiLCkgKyBnZ3RpdGxlKCJyZXMgPSAwLjIiKQpTcGF0aWFsRGltUGxvdChkZi4yLCBncm91cC5ieSA9ICJTcGF0aWFsX3Nubl9yZXMuMC4zIiwpICsgZ2d0aXRsZSgicmVzID0gMC4zIikKU3BhdGlhbERpbVBsb3QoZGYuMiwgZ3JvdXAuYnkgPSAiU3BhdGlhbF9zbm5fcmVzLjAuNCIpICsgZ2d0aXRsZSgicmVzID0gMC40IikKU3BhdGlhbERpbVBsb3QoZGYuMiwgZ3JvdXAuYnkgPSAiU3BhdGlhbF9zbm5fcmVzLjAuNiIpICsgZ2d0aXRsZSgicmVzID0gMC42IikKU3BhdGlhbERpbVBsb3QoZGYuMiwgZ3JvdXAuYnkgPSAiU3BhdGlhbF9zbm5fcmVzLjAuOCIpICsgZ2d0aXRsZSgicmVzID0gMC44IikKU3BhdGlhbERpbVBsb3QoZGYuMiwgZ3JvdXAuYnkgPSAiU3BhdGlhbF9zbm5fcmVzLjEiKSArIGdndGl0bGUoInJlcyA9IDEiKQpTcGF0aWFsRGltUGxvdChkZi4yLCBncm91cC5ieSA9ICJTcGF0aWFsX3Nubl9yZXMuMS4yIikgKyBnZ3RpdGxlKCJyZXMgPSAxLjIiKQpTcGF0aWFsRGltUGxvdChkZi4yLCBncm91cC5ieSA9ICJTcGF0aWFsX3Nubl9yZXMuMSIpICsgZ2d0aXRsZSgicmVzID0gMS40IikKU3BhdGlhbERpbVBsb3QoZGYuMiwgZ3JvdXAuYnkgPSAiU3BhdGlhbF9zbm5fcmVzLjEuMiIpICsgZ2d0aXRsZSgicmVzID0gMS42IikKYGBgCgpOb3cgd2Ugd2FudCB0byBjaG9vc2UgYSByZXNvbHV0aW9uIHZhbHVlLiBPbmUgbWV0aG9kIHRvIGRvIHRoaXMgdXNlcyB0aGUgUiBwYWNrYWdlIENsdXN0cmVlLiBUaGlzIGlzIHRoZSBbUiBwYWNrYWdlIGRlc2NyaXB0aW9uXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvY2x1c3RyZWUvaW5kZXguaHRtbCk6ICJEZWNpZGluZyB3aGF0IHJlc29sdXRpb24gdG8gdXNlIGNhbiBiZSBhIGRpZmZpY3VsdCBxdWVzdGlvbiB3aGVuIGFwcHJvYWNoaW5nIGEgY2x1c3RlcmluZyBhbmFseXNpcy4gT25lIHdheSB0byBhcHByb2FjaCB0aGlzIHByb2JsZW0gaXMgdG8gbG9vayBhdCBob3cgc2FtcGxlcyBtb3ZlIGFzIHRoZSBudW1iZXIgb2YgY2x1c3RlcnMgaW5jcmVhc2VzLiBUaGlzIHBhY2thZ2UgYWxsb3dzIHlvdSB0byBwcm9kdWNlIGNsdXN0ZXJpbmcgdHJlZXMsIGEgdmlzdWFsaXNhdGlvbiBmb3IgaW50ZXJyb2dhdGluZyBjbHVzdGVyaW5ncyBhcyByZXNvbHV0aW9uIGluY3JlYXNlcy4iIEl0IHdpbGwgZ2VuZXJhdGUgYSB0cmVlIGRpYWdyYW0gc2hvd2luZyBob3cgdGhlIGRpZmZlcmVudCBjbHVzdGVyaW5ncyBhcmUgaW50ZXItcmVsYXRlZC4gVGhlIGNsdXN0ZXJzIGluIHRoaXMgZGlhZ3JhbSB3aWxsIGJlIGNvbG91cmVkIGRpZmZlcmVudCBzaGFkZXMgb2YgYmx1ZSwgcmVwcmVzZW50aW5nICJzYzMgc3RhYmlsaXR5Ii4gVGhpcyBpcyBhICJTdGFiaWxpdHkgaW5kZXggW3RoYXRdIHNob3dzIGhvdyBzdGFibGUgZWFjaCBjbHVzdGVyIGlzIGFjY3Jvc3MgdGhlIHNlbGVjdGVkIHJhbmdlIG9mIGsuIFRoZSBzdGFiaWxpdHkgaW5kZXggdmFyaWVzIGJldHdlZW4gMCBhbmQgMSwgd2hlcmUgMSBtZWFucyB0aGF0IHRoZSBzYW1lIGNsdXN0ZXIgYXBwZWFycyBpbiBldmVyeSBzb2x1dGlvbiBmb3IgZGlmZmVyZW50IGsiCgpgYGB7cn0KY2x1c3QgPC0gY2x1c3RyZWUoZGYuMiwgcHJlZml4ID0gIlNwYXRpYWxfc25uX3Jlcy4iLCBub2RlX2NvbG91ciA9ICJzYzNfc3RhYmlsaXR5IiwgZWRnZV93aWR0aCA9IDEsIG5vZGVfdGV4dF9jb2xvdXIgPSAid2hpdGUiLCBub2RlX2xhYmVsX3NpemUgPSA0LCBsYXlvdXQgPSAidHJlZSIsIGVkZ2VfYXJyb3cgPSBGQUxTRSkKY2x1c3QKZ2dzYXZlKHBsb3QgPSBjbHVzdCwgZmlsZSA9IHBhc3RlMChvdXRkaXIsIHNhbXBsZUlELCAiX2NsdXN0cmVlLnBkZiIpLCB3aWR0aCA9IDEwLCBoZWlnaHQgPSAxMCkKIyBleHRyYWN0IHRoZSBzdGFiaWxpdHkgdmFsdWVzIGZvciB0aGUgZGlmZmVyZW50IHJlc29sdXRpb25zCnN0YWJpbGl0eSA8LSBjbHVzdCRkYXRhWyxjKCJTcGF0aWFsX3Nubl9yZXMuIiwgInNjM19zdGFiaWxpdHkiKV0Kd3JpdGUudGFibGUoc3RhYmlsaXR5LCBmaWxlID0gcGFzdGUwKG91dGRpciwgImNsdXN0cmVlX3N0YWJpbGl0eS50eHQiKSwgc2VwID0gIlx0IiwgcXVvdGUgPSBGQUxTRSwgcm93Lm5hbWVzID0gRkFMU0UsIGNvbC5uYW1lcyA9IFRSVUUpCmBgYAoKYGBge3J9CiMgaGVyZSB3ZSdyZSBnb2luZyB0byB3b3JrIG91dCB3aGljaCBvZiB0aGUgcG9zc2libGUgY2x1c3RlcmluZyByZXNvbHV0aW9ucyBpcyB0aGUgbW9zdCBzdGFibGUgKGkuZS4gZ2l2ZXMgdGhlIGhpZ2hlc3QgYXZlcmFnZSBzYzMgc3RhYmlsaXR5IHNjb3JlKS4gCnN0YWJpbGl0eSA8LSBzdGFiaWxpdHlbc3RhYmlsaXR5JFNwYXRpYWxfc25uX3Jlcy4gJWluJSBuYW1lcyh3aGljaCh0YWJsZShzdGFiaWxpdHkkU3BhdGlhbF9zbm5fcmVzLikgPiAxKSksIF0Kc3RhYmlsaXR5LmF2ZSA8LSBhZ2dyZWdhdGUoYXMubnVtZXJpYyhzdGFiaWxpdHkkc2MzX3N0YWJpbGl0eSksIGxpc3Qoc3RhYmlsaXR5JFNwYXRpYWxfc25uX3Jlcy4pLCBtZWFuKQpyb3duYW1lcyhzdGFiaWxpdHkuYXZlKSA8LSBzdGFiaWxpdHkuYXZlJEdyb3VwLjEKc3RhYmlsaXR5LmF2ZSRHcm91cC4xIDwtIE5VTEwKc3RhYmlsaXR5LmF2ZS5ubzAgPC0gc3RhYmlsaXR5LmF2ZVsyOm5yb3coc3RhYmlsaXR5LmF2ZSksICwgZHJvcCA9IEZBTFNFXQpiZXN0cmVzIDwtIGFzLm51bWVyaWMocm93bmFtZXMoc3RhYmlsaXR5LmF2ZS5ubzApW3doaWNoLm1heChzdGFiaWxpdHkuYXZlLm5vMCR4KV0pCnN0YWJpbGl0eS5hdmUKYmVzdHJlcwpgYGAKClRoZSB2YWx1ZSBwcmludGVkIGFib3ZlIGlzIHRoZSByZXNvbHV0aW9uIHBhcmFtZXRlciB0aGF0IHByb2R1Y2VkIHRoZSBoaWdoZXN0IGF2ZXJhZ2Ugc3RhYmlsaXR5LgoKYGBge3J9CnJtKGRmLjIpCmRmIDwtIEZpbmRDbHVzdGVycyhkZiwgcmVzb2x1dGlvbiA9IGJlc3RyZXMpCm15Y29sIDwtIHBhc3RlMCgiU3BhdGlhbF9zbm5fcmVzLiIsIGJlc3RyZXMpCkRpbVBsb3QoZGYsIGdyb3VwLmJ5ID0gbXljb2wpCmdnc2F2ZShwYXN0ZTAob3V0ZGlyLCBzYW1wbGVJRCwgIlVNQVBfcmVzIiwgYmVzdHJlcywgIi5wZGYiKSkKU3BhdGlhbERpbVBsb3QoZGYsIGdyb3VwLmJ5ID0gbXljb2wpCmdnc2F2ZShwYXN0ZTAob3V0ZGlyLCBzYW1wbGVJRCwgInNwYXRpYWxfcmVzIiwgYmVzdHJlcywgIi5wZGYiKSkKYGBgCgojIEZJTkQgTUFSS0VSIEdFTkVTIEFTU09DSUFURUQgV0lUSCBFQUNIIENMVVNURVIKCmBgYHtyfQpteWNvbCA8LSBwYXN0ZTAoIlNwYXRpYWxfc25uX3Jlcy4iLCBiZXN0cmVzKQpJZGVudHMoZGYpIDwtIG15Y29sCm1hcmtlcnMgPC0gRmluZEFsbE1hcmtlcnMoZGYsIG9ubHkucG9zID0gVFJVRSwgbWluLnBjdCA9IDAuMjUsIGxvZ2ZjLnRocmVzaG9sZCA9IDAuMjUpCnRvcDEwIDwtIG1hcmtlcnMgJT4lIGdyb3VwX2J5KGNsdXN0ZXIpICU+JSB0b3BfbihuID0gMTAsIHd0ID0gYXZnX2xvZzJGQykKd3JpdGUudGFibGUobWFya2VycywgZmlsZSA9IHBhc3RlMChvdXRkaXIsICJhbGxtYXJrZXJzLnR4dCIpLCBzZXAgPSAiXHQiLCBxdW90ZSA9IEZBTFNFLCBjb2wubmFtZXMgPSBOQSkKd3JpdGUudGFibGUodG9wMTAsIGZpbGUgPSBwYXN0ZTAob3V0ZGlyLCAidG9wMTBtYXJrZXJzLnR4dCIpLCBzZXAgPSAiXHQiLCBxdW90ZSA9IEZBTFNFLCBjb2wubmFtZXMgPSBOQSkKYGBgCgpgYGB7cn0KRG9IZWF0bWFwKGRmLCBmZWF0dXJlcyA9IHRvcDEwJGdlbmUpICsgTm9MZWdlbmQoKQpnZ3NhdmUocGFzdGUwKG91dGRpciwgInRvcDEwX2hlYXRtYXAucGRmIikpCmBgYAoKIyBGSU5EIFRIRSBOVU1CRVIgT0YgQ0VMTFMgUEVSIENMVVNURVIKCmBgYHtyfQp0YWJsZSA8LSBhcy5kYXRhLmZyYW1lKHRhYmxlKGRmW1tteWNvbF1dKSkKZ2dwbG90KHRhYmxlLCBhZXMoeCA9IFZhcjEsIHkgPSBGcmVxKSkgKwogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKSArCiAgY29vcmRfZmxpcCgpCmdnc2F2ZShmaWxlID0gcGFzdGUwKG91dGRpciwgImNsdXN0ZXJjb3VudHMucGRmIikpCnJvd25hbWVzKHRhYmxlKSA8LSB0YWJsZSRWYXIxCnRhYmxlJFZhcjEgPC0gTlVMTAp3cml0ZS50YWJsZSh0YWJsZSwgZmlsZSA9IHBhc3RlMChvdXRkaXIsICJjbHVzdGVyY291bnRzLnR4dCIpLCBzZXAgPSAiXHQiLCBxdW90ZSA9IEZBTFNFLCBjb2wubmFtZXMgPSBOQSkKYGBgCgojIFNBVkUgVEhFIE9VVFBVVAoKYGBge3J9CnNhdmVSRFMoZGYsIGZpbGUgPSBwYXN0ZTAob3V0ZGlyLCBzYW1wbGVJRCwgIl9ERl9hbm5vdGF0ZWQuUkRTIikpCmBgYA==